diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml new file mode 100644 index 000000000..64103ffff --- /dev/null +++ b/.github/workflows/action.yml @@ -0,0 +1,29 @@ +name: CI +on: + push: + branches: + - dev +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v2 + + - name: Install dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: yarn + + - name: Hexo clean + run: ./node_modules/.bin/hexo clean + + - name: Hexo generate + run: ./node_modules/.bin/hexo generate + + - name: Deploy to master + uses: peaceiris/actions-gh-pages@v3 + with: + personal_token: ${{ secrets.CI_TOKEN }} + external_repository: BladeCode/BladeCode.github.io + publish_branch: master + publish_dir: ./public diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..9af881926 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +Thumbs.db +db.json +*.log +node_modules/ +public/ +.deploy*/ +package-lock.json +.vscode/ \ No newline at end of file diff --git a/.nojekyll b/.nojekyll deleted file mode 100644 index e69de29bb..000000000 diff --git a/2018/03/25/hexo-blog/index.html b/2018/03/25/hexo-blog/index.html deleted file mode 100644 index a0617e836..000000000 --- a/2018/03/25/hexo-blog/index.html +++ /dev/null @@ -1,616 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Hexo Blog 搭建 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Hexo Blog 搭建 -

- - -
- - - - -

之前一直纠结用Jekyll还是Hexo来搭建GitHub Page博客,原本一直想搭建一个Material Design主题风格,从Hexo Themes中寻找到一款不错的主题,indigo是一款支持IE10+,评论,目录导航,分享等功能的轻量Blog主题。

-

简单的修改了该主题之后,本地预览都没有什么问题,但是部署到Github上,样式什么的都无法加载,应该是我的操作姿势不对吧,调整了半天没有解决,烦躁中找到之前star的另一款很受欢迎的Next主题。

-

既然自己修改的无法正常部署预览,那就用别人写好的吧,刚好赶上Next新版本V6.0系列的推出,那就不废话,直接开干

- -

材料准备

-
    -
  • Node LTS
  • -
  • Git
  • -
  • Hexo
  • -
  • Next
  • -
-

安装

-

Node,Git的安装过程略

-

Hexo

-
    -
  1. Hexo 安装
    1
    $ npm install hexo-cli -g
    -
  2. -
  3. 初始化
    1
    $ hexo init <your blog name>
    -
  4. -
  5. 安装依赖包
    1
    2
    $ cd <you blog name>
    $ npm install
    -
  6. -
  7. 启动服务预览
    1
    $ hexo serve
    -
  8. -
-

Next

-
    -
  1. -

    安装Next 主题

    -
    1
    $ git clone https://github.com/theme-next/hexo-theme-next themes/next
    -
    -

    当前操作在 blog的根目录下执行

    -
    -
  2. -
  3. -

    修改Blog 配置
    -you blog name 根目录 _config.yml

    -
      -
    • theme: 由原来默认landscape更改位next(大约:76行)
    • -
    • 其他配置项,根据自己的需求进行更改,我这里更改了title,subtitle,author,language,url配置,其中language如果没有修改,默认为英文语言,在V6.0系列由原来zh-Hans更新为zh-CN
    • -
    • 添加部署到Github配置
    • -
    -
    1
    2
    3
    4
    deploy:
    type: git
    repo: https://github.com/BladeCode/BladeCode.github.io.git # 用户名仓库
    branch: master # 用户名仓库的分支应该指定master,master分支也可以不用写
    -
  4. -
  5. -

    修改Theme 配置
    -路径:you blog name/Themes/next/_config.yml
    -这里不罗嗦了,其配置可参考hexo-theme-next项目README文件

    -
  6. -
-

部署

-

上面已经配置好了部署的目标仓库,那么这里直接使用Hexo提供的部署命令即可

-
1
$ hexo d
-

相关命令介绍等,请查看官方文档说明

-

部署完成后,可以直接访问 http://`you blog name`/github.io

-

自定义域名

-

虽然现在 blog 可以使用 Github 提供的项目二级域名来访问,为了个性化以及方便等,配置自己的域名

-
    -
  1. -

    登录域名所属的管理网站(这里以阿里云域名服务为例)
    -gitpages-domain-manger

    -
  2. -
  3. -

    添加解析

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $ # 解析一
    记录类型:CNAME
    主机记录:www
    记录值:bladecode.github.io
    解析路线:default

    $ # 解析二
    记录类型:A
    主机记录:@
    记录值:192.30.252.153
    解析路线:default

    $ # 解析三
    记录类型:A
    主机记录:@
    记录值:192.30.252.154
    解析路线:default
    -
    -

    192.30.252.153是GitHub的地址,你也可以ping你的 http://xxxx.github.io 的ip地址,填入进去

    -
    -
  4. -
  5. -

    修改Github上项目的domain设置
    -gitpages-domain-custom

    -
  6. -
  7. -

    添加CNAME文件
    -保存路径:you blog name/source
    -新增文件:CNAME 文件 (格式要求:保存成所有文件而不是txt文件)
    -CNAME 文件内容:incoder.org

    -
  8. -
-
-

如果带有www,那么以后访问的时候必须带有www完整的域名才可以访问,但如果不带有www,以后访问的时候带不带www都可以访问。所以建议,不要带有www

-
-

Https开启

-

开启Https 需要借助Cloudflare,关于Cloudflare的介绍等不在这里展开

-
    -
  1. 注册账号
  2. -
  3. Add website
    -site
  4. -
  5. Querying your DNS
    -query
  6. -
  7. Select Plan
    -plan
  8. -
  9. 域名解析记录获取
    -continue
  10. -
  11. DNS 对比,并修改Cloudflare提供的DNS来解析
    -change
  12. -
  13. 域名管理后台,修改DNS
    -dns -
    -

    阿里云服务相关域名DNS修改帮助文档

    -
    -
  14. -
  15. 成功激活
    -active
  16. -
  17. SSL证书申请提醒
    -cer
  18. -
  19. 添加强制HTTPS规则
    -rule
  20. -
  21. 规则制定
    -deploy
  22. -
-

好了剩下的就是等证书颁发,可能要等上一些时间,具体每个人不尽相同,这里就不多做解释了。

-

Let’s all,本次的Hexo的相关初级教程就到这里

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/27/rap1/index.html b/2018/03/27/rap1/index.html deleted file mode 100644 index f51f29106..000000000 --- a/2018/03/27/rap1/index.html +++ /dev/null @@ -1,570 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Api 文档管理系统 RAP1环境搭建 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Api 文档管理系统 RAP1环境搭建 -

- - -
- - - - -

前后端分离的路上,一款强大的API管理工具,可以降低沟通成本,大大提高开发效率,节省的时间,让我们去做更有意义的事情。

-

API管理工具有很多,选择适合自身需求的就是最好

-

这里以阿里妈妈出品的RAP产品;目前RAP分为: RAP1RAP2

- -
-

虽然RAP1不再添加新功能,只做维护工作,介于RAP2目前还不是很成熟,本篇文章先讲RAP1的搭建过程(虽然官方Wiki已经有很详细的部署教程,但在部署过程中还是遇到一些问题,因此就记录下来)

-
-

如果你不需要搭建,可以直接访问RAP1提供的服务http://rapapi.org

-

项目构建

-
    -
  • 系统环境:Windows 10 x64
  • -
  • 应用工具:GitIDEAJDK1.8+Tomcat8+MySQLRedis3+
  • -
-

这里Git,IDEA,JDK1.8,Tomcat8,MySQL不再赘述安装步骤以及环境配置

-

安装基本工具

-

Redis

-

由于Redis 官方并未支持Windows系统,因此借助MicrosoftArchive团队所提供的Windows Redis安装包,这里下载最新的Redis-x64-3.2.100.msi

-
    -
  • -

    以管理员身份运行安装包Redis-x64-3.2.100.msi

    -
      -
    1. 添加环境变量
      -env
    2. -
    3. 默认6379端口
      -port
    4. -
    5. 检查Redis服务,是否已经启动
      -serve
    6. -
    -
    -

    其他默认即可,不要设置Memory Limit

    -
    -
  • -
-

构建项目

-

获取源代码

-
1
2
git clone git@github.com:thx/RAP.git
git checkout release
-
-

确保您正确的切换到release分支,否则会出现少包,因为master分支引用一些不对外公开的内部组件,不提供给外部用户使用

-
-

导入到IDEA

-

IDEA==>Open==>RAP

-

初始化数据库

-

执行脚本文件:RAP\src\main\resources\database\initialize.sql

-

修改配置文件

-

文件:RAP\src\main\resources\database\config.properties
-修改:数据库用户名密码
-update

-

启动项目

-
    -
  1. Edit config
    -config
  2. -
  3. Create Tomcat
    -create
  4. -
  5. Deploy war
    -deploy
  6. -
  7. Deploy success
    -success
  8. -
-

注意成功部署后,请注册新账号登录

-

至此,RAP1的本机部署已经完成。

-

其他

-
    -
  • RAP1学习中心
    -部分同学无法查看视频,请异步至issues
  • -
  • RAP1 Wiki文档
  • -
  • Mockjs
  • -
  • RAP2环境搭建教程
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/27/rap2/index.html b/2018/03/27/rap2/index.html deleted file mode 100644 index a253ee069..000000000 --- a/2018/03/27/rap2/index.html +++ /dev/null @@ -1,682 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Api 文档管理系统 RAP2环境搭建 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Api 文档管理系统 RAP2环境搭建 -

- - -
- - - - -

RAP2是采用前后端分离的形式,因此搭建完整的RAP2需要 服务端:rap2-delos客户端:rap2-dolores 同时部署

-

部署RAP2需要亲具有Node+Linux+MySQL的运维知识,如果亲对此不是很了解,建议用http://rap2.taobao.org 线上版本就可以

-

由于 客户端:rap2-dolores 是建立在 服务端:rap2-delos 基础上,因此先搭建服务端应用

- -

个人贡献 📖 issues 119

-
    -
  • 截至 2018-08-01 delos 并没有发布 Tag版本,应该还处于功能开发前期阶段吧。本教程是在CentOS机器上实战部署
  • -
  • 然而安装部署并不是顺利,因此记录踩过的坑(别问我为啥不用Docker,因为我司分配的机器无法满足Docker的最低内核版本),安装环境介绍:Redis,delos,dolores均在一台服务器,MySQL使用已存在的服务
  • -
  • 本篇文章最后更新于 2018-08-01,因此后续的项目部署相关,请参考官方部署教程
  • -
-
-

安装基本工具

-
    -
  • Git
  • -
  • Node 8.9.4+
  • -
  • Redis 4.0+
  • -
  • MySQL 5.7+
  • -
-

以上基本工具请根据自身需要,下载对应系统安装包,请自行解决安装配置等问题,这里不做过多说明

-
-

Redis 安装可参考Linux 常用应用安装
-Redis 最好用非安全模式启动

-
-

服务端delos环境搭建

-

构建项目

-
-

构建项目前,请确认Node,Redis,MySQL服务均能正常使用

-
-
1
git clone https://github.com/thx/rap2-delos.git
-

环境配置

-

创建数据库

-
    -
  • -

    Mac or Linux

    -
    1
    mysql -e 'CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP DEFAULT CHARSET utf8 COLLATE utf8_general_ci';
    -
  • -
  • -

    Windows 环境

    -

    进入mysql命令后执行

    -
    1
    CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
    -
  • -
-

配置文件

-

目录:rap2-delos/src/config
-文件:config.dev.ts;其中dev,表示开发环境,其他同理
-修改:config.dev.ts文件中db对象中usernamepassword参数与本地或者开发环境的数据库信息匹配

-

启动项目

-

安装项目依赖包

-

项目根目录下执行

-
1
2
3
4
# 安装项目所需依赖
npm install
# 全局安装PM2
npm install -g pm2
-

安装TypeScript编译包

-
1
npm install typescript -g
-
-

如果下载缓慢,请使用淘宝npm镜像

-
-

初始化数据库

-

项目根目录下执行(该过程比较慢,耐心等待初始化完成)

-
1
npm run create-db
-

编译启动项目

-

执行mocha测试用例和js代码规范检查

-
1
npm run check
-
    -
  • 开发模式
    -启动开发模式的服务器 监视并在发生代码变更时自动重启(第一次运行比较慢,请耐心等待)
    1
    npm run dev
    -
  • -
  • 生产模式
    -启动生产模式服务器
    1
    npm start
    -
  • -
-

看到浏览器中如下提示,表示服务端delos已经部署成功

-
-

RAP2后端服务已启动,请从前端服务(rap2-dolores)访问。 RAP2 back-end server is started, please visit via front-end service (rap2-dolores).

-
-

或者在程序控制台出现如下Log,表示服务端delos已经部署成功
-delos

-

常见问题

-

部署问题

-
    -
  1. -

    Windows下执行 npm run build,提示'rm' 不是内部或外部命令,也不是可运行的程序或批处理文件

    -

    原因:rm 是Linux下命令,
    -解决方法:Windows系统可使用 git bash 打开该项目,执行该命令

    -
  2. -
  3. -

    执行 npm run create-db 命令,提示
    -Unable to connect to the database:{ SequelizeAccessDeniedError: Access denied for user 'root'@'localhost' (using password:NO)}

    -

    原因:未修改 rap2-delos/src/config 目录下数据库配置文件,或者是与文件中的数据库信息与之连接的数据库信息不匹配
    -解决方法:修改 config.dev.ts 文件数据库配置信息

    -
    -

    如果修改正确无误后,执行 npm run create-db 依旧出错,那么查看该项目中是否已经存在 dist 目录,如果有,请按照如上修改对应的数据库配置信息

    -
    -
  4. -
  5. -

    执行 npm run dev 命令,提示 Error: listen EADDRINUSE :::8080
    -原因:8080端口被占用
    -解决方法:杀掉占用8080端口的应用

    -
  6. -
  7. -

    执行 npm install 命令,提示 hiredis 编译无法通过
    -原因:无权限操作rap2-delos/node_modules/hiredis路径
    -解决方法:sudo npm install

    -
    -

    如果提示sudo: npm: command not found,请参考 stackoverflow-npmstackoverflow-node

    -
    -
  8. -
  9. -

    执行 npm run dev 可以正常启动,npm start 命令无法正常启动服务
    -原因:请使用 pm2 logs 查看日志具体定位
    -示例:由于Redis的安全模式,不能正常使用

    -
    1
    2
    3
    4
    5
    6
    7
    ReplyError: Ready check failed: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 

    1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent.
    2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server.
    3) If you started the server manually just for testing, restart it with the '--protected-mode no' option.
    4) Setup a bind address or an authentication password.
    NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.
    -

    解决方法: 使用--protected-mode no方式启动

    -
  10. -
-

客户端dolores环境搭建

-

构建项目

-

获取源代码

-
1
git clone https://github.com/thx/rap2-dolores.git
-

环境配置

-

配置文件

-

目录:rap2-dolores/src/config
-文件:config.dev.ts;其中dev,表示开发环境,其他同理
-修改:config.dev.ts 配置文件 serve 的地址,更改为 服务端rap2-delos)部署成功后的地址,默认:'http://localhost:8080'

-

启动项目

-

安装项目依赖包

-

项目根目录下执行

-
1
npm install
-
-

如果下载缓慢,请使用淘宝npm镜像

-
-

编译启动项目

-
    -
  • 开发模式
    -自动监视改变后重新编译
    1
    npm run dev
    -备注:测试用例
    1
    npm run test
    -
  • -
  • 生产模式
    -编译React生产包
    1
    npm run build
    -用serve命令或nginx服务器路由到编译产出的build文件夹作为静态服务器即可
    1
    serve -s ./build -p 80
    -
  • -
-

看到浏览器中出现登录页面,表示部署成功
-dolores

-

常见问题

-

部署问题

-
    -
  1. -

    执行npm run dev,提示

    -
    1
    2
    3
    return process.dlopen(module,path._makeLong(filename))
    ...
    ...node_modules\node-sass\vendor\win32-x64-57\binding.node is not a valid Win32 application...
    -

    原因:项目依赖包node-sass没有安装完全
    -解决方法:npm install node-sass

    -
  2. -
  3. -

    项目运行起来,但一直停留在加载动画那里

    -

    浏览器控制台输出:

    -
    1
    2
    GET http://127.0.0.1:8080/account/info  ==>>
    Failed to load http://127.0.0.1:8080/account/info
    -

    原因:未修改rap2-delos/src/config目录下服务端连接地址,或者修改结果与rap2-dolores实际提供服务地址不匹配
    -解决方法:修改config.dev.ts文件serve配置信息

    -
    -

    如果Windows系统修改正确无误后,依旧出错,查看hosts(路径:C:\Windows\System32\drivers\etc)中127.0.0.1的IP前是否有#,如果有请取消注释

    -
    -
  4. -
-

其他

-

MySQL 运行问题

-
    -
  • 错误一
    -mysql
    -原因:MySQL 集成命令没有加入系统的环境变量
    -解决方法:将安装的MySQL Service路径加入系统变量
    -path
  • -
  • 错误二
    -create
    -原因:没有数据库链接权限
    -解决方法:先登录用root数据库,密码具体看自己数据库当时设置的密码
  • -
-

如何获取更新

-

目前请选择 master 分支源码,后续其他分支请看相应分支说明文档。在开发环境中git pull来获取最新的源码更新,每一期更新都会有对应的update.md请关注并按照上面的指示进行升级工作。

-

附录

-
    -
  • Redis如何后台启动
  • -
  • Redis配置文件介绍
  • -
  • PM2实用入门指南
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/13/gitignore/index.html b/2018/04/13/gitignore/index.html deleted file mode 100644 index 605026f18..000000000 --- a/2018/04/13/gitignore/index.html +++ /dev/null @@ -1,532 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -.gitignore 基础知识 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- .gitignore 基础知识 -

- - -
- - - - -

.gitignore顾名思义是Git中用来管理所需要忽略或者说不用纳入版本控制文件

-

基本配置语法

-
    -
  1. “#“:表示注释
  2. -
  3. “/“:表示目录
  4. -
  5. “*“:表示通配符,用来通配多个字符
  6. -
  7. “?“:表示通配单个字符
  8. -
  9. “[]“:表示包含单个字符的匹配列表
  10. -
  11. “!“:表示不忽略匹配到的文件或者目录
  12. -
- -
-

注意:Git对.gitignore配置文件是从上往下进行规则匹配,这也意味如果:前(limit)>后(limit),则后面的规则不会被执行

-
-

全局与局部

-

.gitignore分为: 全局 ignore,局部 ignore

-

全局ignore设置

-
    -
  • 在用户账户文件夹(C:\Users<‘YourName’>)路径下新建一个命名为.gitignore_global的文件
  • -
  • 使用Git Bash(需要切换路径到C:\Users<‘YourName’>)或者Git CMD命令行工具输入:
    1
    git config --global core.excludesfile ~/.gitignore_global
    -
  • -
  • 此时全局ignore已经设置完成,你只需要修改.gitignore_global文件内需要忽略的文件类型就可以全局控制忽略不需要纳入版本控制的文件或文件夹
  • -
  • 不难发现,其实是往 .gitconfig中加入如下内容来指名Git忽略不纳入版本控制的文件,当然如果你不想用命令行完成全局设置,你也可以直接在.gitconfig文件中加入[core] excludesfile= ~/.gitignore_global内容即可
  • -
-

局部ignore设置

-
    -
  • 只需要在Git控制版本控制项目的根目录中加入.gitignore文件,在.gitignore文件中写明忽略不纳入版本控制的文件即可
  • -
-

参考示例

-
-

你可以查看参考Github官方所写好的示例

-
-

插件.ignore

-

支持Android Studio,JetBrains系列
-安装方法

-
    -
  • Settings > Plugs > Browse repositories > .ignore > Install plugin
  • -
  • 里面有已经写好的模板,只需适当修改
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/24/gitlab1/index.html b/2018/04/24/gitlab1/index.html deleted file mode 100644 index 2d0abd6c2..000000000 --- a/2018/04/24/gitlab1/index.html +++ /dev/null @@ -1,599 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Gitlab 应用搭建 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Gitlab 应用搭建 -

- - -
- - - - -

我司团队之前一直使用SVN来进行代码托管,主要问题

-
    -
  1. 每次来个新人都需要找对应的SVN管理员进行授权分配指定的仓库操作权限,有时候需要多个项目切换,还得再次提出进行仓库的指定
  2. -
  3. SVN都是以中文命名,这其实没啥,但是在eclipse 以及IDEAXcode等开发工具,链接地址都会把中文字进行编码,造成路径非常的长,强迫症的我这怎么忍得了
  4. -
  5. 产品相关的,设计相关的啥也都放在SVN里面,搞得SVN里面鱼龙混杂
  6. -
- -

因此在我提出及建议下,部门经理同意了对代码的管理进行隔离方便有效的对代码的授权监管,并同时制定代码的相关规范和服务的自动化部署等,提高团队的开发效率和代码质量。

-

本节主要介绍Gitlab的环境搭建和基础的功能配置

-

目的:

-
    -
  1. 搭建Gitlab服务
  2. -
  3. 和公司AD域账号关联,用域账号直接登录Gitlab
  4. -
  5. 挂载Gitlab 仓库到指定存储位置
  6. -
-

Gitlab安装

-

环境

-
    -
  • OS:CentOS 7
  • -
  • Gitlab:Gitlab CE 10.6.4
  • -
-
-

Gitlab 版本

-
-
    -
  • Gitlab Community Edition (CE):社区版,免费,用户自行托管,通过社区提供技术支持
  • -
  • Gitlab Enterprise Edition (EE):企业版,付费,用户自行托管,提供附加的功能以及技术支持
  • -
  • Gitlab.com:免费的SaaS服务,可以创建共有以及私有的版本库,可以购买额外的技术支持
  • -
  • GitHost.io:由Gitlab提供的用户私有的独享服务
  • -
-

Gitlab部署

-
    -
  1. -

    系统防火墙中打开HTTP和SSH访问

    -
    1
    2
    3
    4
    5
    6
    sudo yum install -y curl policycoreutils-python openssh-server
    sudo systemctl enable sshd
    sudo systemctl start sshd

    sudo firewall-cmd --permanent --add-service=http
    sudo systemctl reload firewalld
    -
  2. -
  3. -

    安装Postfix发送通知邮件。如果您想使用其他解决方案发送电子邮件,请跳过此步骤并在安装GitLab后配置外部SMTP服务器

    -
    1
    2
    3
    sudo yum install postfix
    sudo systemctl enable postfix
    sudo systemctl start postfix
    -
  4. -
  5. -

    添加GitLab软件包存储库

    -
    1
    curl -LJO https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-10.0.0-ce.0.el7.x86_64.rpm
    -
  6. -
  7. -

    安装软件包

    -
    1
    rpm -i gitlab-ce-10.0.0-ce.0.el7.x86_64.rpm
    -

    完成安装如下日志显示:

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
         *.                  *.
    *** ***
    ***** *****
    .****** *******
    ******** ********
    ,,,,,,,,,***********,,,,,,,,,
    ,,,,,,,,,,,*********,,,,,,,,,,,
    .,,,,,,,,,,,*******,,,,,,,,,,,,
    ,,,,,,,,,*****,,,,,,,,,.
    ,,,,,,,****,,,,,,
    .,,,***,,,,
    ,*,.



    _______ __ __ __
    / ____(_) /_/ / ____ _/ /_
    / / __/ / __/ / / __ \`/ __ \
    / /_/ / / /_/ /___/ /_/ / /_/ /
    \____/_/\__/_____/\__,_/_.___/

    -
  8. -
  9. -

    编译配置文件

    -
    1
    2
    cd /opt/gitlab/bin
    ./gitlab-ctr reconfigure
    -
  10. -
  11. -

    启动服务

    -
    1
    ./gitlab-ctl start
    -
    -
      -
    • 成功启动服务,默认路径访问:http://localhost:80
    • -
    • 默认安装位置 /opt/gitlab/
    • -
    • 配置文件默认路径 /etc/gitlab/gitlab.rb
    • -
    • 默认账号:root,密码:5iveL!fe
    • -
    -
  12. -
-

常用配置项修改

-

以下配置项的修改,完成后均需要重新编译文件(配置文件默认路径 /etc/gitlab/gitlab.rb),默认,并重启Gitlab服务

-

访问地址

-

修改external_url为Gitlab对应机器IP所配置的域名
-gitlab-url

-

LDAP启用

-

修改host,port,bind_dn,password,base参数即可
-gitlab-ladp

-

各参数解释:

-
    -
  • hostport 是 LDAP 服务的主机地址及端口
  • -
  • bind_dn 和 password 是一个管理 LDAP 的 dn 及密码
  • -
  • base 表示 LDAP 将以该 dn 为 节点,向下查找用户
  • -
  • user_filter 表示以某种过滤条件筛选用户
  • -
  • attributes 表示 GitLab 中的字段与 LDAP 中哪些字段可以相互对应,比如可以用 LDAP 中的 uid 来作为 GitLab 用户名
  • -
-

编译重启后,查看登录是否已经显示LDAP登录入口

-

gitlab-ldap-login

-

为了安全我们需要关闭 GitLab 自己的注册功能,这样新用户只能通过 LDAP 认证的方式进行登陆。

-

gitlab-sign-up

-

存储仓库修改

-

默认仓库存储位置:/var/opt/gitlab/git-data/repositories/
-gitlab-dirs

-

Gitlab日志

-

默认日志位置: /var/log/gitlab

-
1
2
cd /opt/gitlab/bin
gitlab-ctl tail -f nginx/gitlab_access.log
-

或者在Gitlab服务的系统设置中查看
-gitlab-logs

-

附录

-
    -
  • 官方安装教程
  • -
  • 官方配置文件
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/24/realm/index.html b/2018/04/24/realm/index.html deleted file mode 100644 index 76a41f0f3..000000000 --- a/2018/04/24/realm/index.html +++ /dev/null @@ -1,600 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Realm 数据库快速上手 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Realm 数据库快速上手 -

- - -
- - - - -

realm-db

- -

Android 供了多种选项来保存永久性应用数据。

-
    -
  • Shared preferences
  • -
  • Internal file storage
  • -
  • External file storage
  • -
  • Databases
  • -
  • Network
  • -
-

其中数据库存储是一种必备技能,而衍生的mobile db也是层出不穷,本节主要介绍全平台(除Android,iOS,macOS外还支持web,桌面应用)Realm数据库在Android上的使用

-

快速上手

-
    -
  • Android Studio 1.5.1+
  • -
  • JDK1.7+
  • -
  • Android API 9+
  • -
  • Realm 默认情况下使用内部存储(internal storage),一般来说,这个文件位于/data/data/<packagename>/files/,文件名:default.realm
  • -
-

集成

-
    -
  • -

    在项目的 build.gradle 文件中添加如下 class path 依赖

    -
    1
    2
    3
    4
    5
    6
    7
    8
    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    classpath "io.realm:realm-gradle-plugin:5.0.0"
    }
    }
    -
  • -
  • -

    在 app 的 build.gradle 文件中应用 realm-android 插件

    -
    1
    apply plugin: 'realm-android'
    -
  • -
-

初始化

-
    -
  • -

    默认初始化

    -
    1
    2
    3
    4
    5
    6
    7
    8
    public class MyApplication extends Application {
    @Override
    public void onCreate() {
    super.onCreate();
    // 默认Realm的配置文件
    Realm.init(this);
    }
    }
    -
  • -
  • -

    自定义初始化

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class MyApplication extends Application {
    @Override
    public void onCreate() {
    super.onCreate();
    // 自定义配置Realm
    initRealm();
    }

    private void initRealm() {
    RealmConfiguration config = new RealmConfiguration.Builder()
    .name("myrealm.realm") // 命名文件名:myrealm.realm
    .inMemory() // 一个非持久化的、存在于内存中的 Realm 实例
    .encryptionKey(getKey()) // 数据库加密key
    .schemaVersion(2) // 数据库结构版本号
    .modules(new MySchemaModule()) // 数据库结构对象
    .migration(new MyMigration()) // 数据库迁移
    .build();
    Realm.setDefaultConfiguration(config);
    }
    }
    -
    -
      -
    1. Realm 实例是线程单例化的,也就是说多次在同一线程调用静态构建器会返回同一 Realm 实例
    2. -
    3. 使用同样的名称同时创建“内存中的”Realm 和常规的(持久化)Realm 是不允许的
    4. -
    -
    -
  • -
-

字段类型

-

Realm 支持以下字段类型:booleanbyteshortintlongfloatdoubleStringDatebyte []。整数类型 shortintlong 都被映射到 Realm 内的相同类型(实际上为 long )。

-
    -
  • @Required修饰类型和空值(null) -
    -

    Realm强制禁止空值(null)被存储
    -只有Boolean,Byte,Short,Integer,Long,Float,Double,String,byte[],Date可被修饰

    -
    -
  • -
  • @Ignore标识一个字段不应该被保存到 Realm
  • -
  • @Index为字段增加搜索索引 -
    -

    仅支持索引的属性类型包括:String,byte,short,int,long,booleanDate

    -
    -
  • -
  • @PrimaryKey -
    -

    必须为字符串(String)或整数(short,int,long)以及它们的包装类型(Short,Int,Long

    -
    -
  • -
-

声明Realm数据模型

-

RealmObject

-

可以把RealmObject 当作POJO使用

-
1
2
3
public class User extends RealmObject {

}
-

RealmModel

-
1
2
3
4
@RealmClass
public class User implements RealmModel {

}
-

关系

-

多对一

-
1
2
3
4
5
6
7
8
9
10
public class Contact extends RealmObject {
private Email email;
// Other fields…
}

public class Email extends RealmObject {
private String address;
private boolean active;
// ... setters and getters left out
}
-

多对多

-
1
2
3
4
5
6
7
8
9
public class Contact extends RealmObject {
public String name;
public RealmList<Email> emails;
}

public class Email extends RealmObject {
public String address;
public boolean active;
}
-

CRUD

-
    -
  • 所有的写操作(添加、修改和删除对象),必须包含在写入事务(transaction)中
  • -
  • 在提交期间,所有更改都将被写入磁盘,并且,只有当所有更改可以被持久化时,提交才会成功。通过取消一个写入事务,所有更改将被丢弃。
  • -
  • 益于 Realm 的 MVCC 架构,当正在进行一个写入事务时读取操作并不会被阻塞!这意味着,除非你需要从多个线程进行并发写入操作,否则,你可以尽量使用更大的写入事务来做更多的事情而不是使用多个更小的写入事务。
  • -
-

-
    -
  • -

    事务执行

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Realm realm = Realm.getDefaultInstance();
    realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
    User user = realm.createObject(User.class);
    user.setName("John");
    user.setEmail("john@corporation.com");
    }
    });
    -
  • -
  • -

    异步事务

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Realm realm = Realm.getDefaultInstance();
    realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm bgRealm) {
    User user = bgRealm.createObject(User.class);
    user.setName("John");
    user.setEmail("john@corporation.com");
    }
    }, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
    // Transaction was a success.
    }
    }, new Realm.Transaction.OnError() {
    @Override
    public void onError(Throwable error) {
    // Transaction failed and was automatically canceled.
    }
    });
    -
    -

    OnSuccess 和 OnError 并不是必须重载的,重载了的回调函数会在事务成功或者失败时在被调用发生的线程执行。

    -
    -
  • -
-

-

-

-

Realm进阶

-

Realm云

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/29/memory-hz1/index.html b/2018/04/29/memory-hz1/index.html deleted file mode 100644 index c2776e1bd..000000000 --- a/2018/04/29/memory-hz1/index.html +++ /dev/null @@ -1,500 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -品·杭州 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 品·杭州 -

- - -
- - - - -

上有天堂,下游苏杭,杭州,一个温文尔雅,一个记忆中天堂,一个南方姑娘的城市。
-杭州:毕业后的第二个城市,很开心在这样的城市生活,工作,结识这里的人,杭州和家乡的气候非常相似,因此在杭州有种在家的感觉,在这里遇到的的人,我都会记着你们美丽帅气的脸庞

- -

18年是一个动荡的一年,曾经的伙伴渐渐的离开了的团队,这两年中,有的人毕业,有的人结婚,有的人生子,有的人成长,感谢我能成为你们生命中的一个过客,和你们一起经历生活百态

-

不管你们在何方,从事着什么样的工作,过着什么样的生活,我会想你们,愿你们的一切顺利

-

粗略的剪影,请异步优酷

-

不遵守规则的人,我们叫他废物,但是,不珍惜同伴的人,连废物都不如
-——宁智波·带土

-
-
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/01/travel-hs/index.html b/2018/05/01/travel-hs/index.html deleted file mode 100644 index 56a4e1f75..000000000 --- a/2018/05/01/travel-hs/index.html +++ /dev/null @@ -1,522 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -忆·黄山 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 忆·黄山 -

- - -
- - - - -
-

黄山归来不看岳

- -
-

五岳未归,先品黄山。以前看黄山还是小学课本《黄山》一文介绍黄山的美,黄山的秀丽,黄山的与众不同,这次是亲身去体验黄山的姿态;趁着五一,趁着年轻,趁着…。废话不多讲,先看黄山日出美景

- - -
-

别问我为啥抖,没有支撑点,全程手持…逃

-
-

这次黄山之行并没有做任何功课,计划到实施前后不超过15天,抱着走一步,看一点的心态去玩,没想到五一节假日,来黄山的人不是很多。

-

出行方式

-

杭州 城西客运站 做大巴直达黄山景区,票价:¥110,时间:大约4小时左右到达

-

攻略

-

逃,没有…
-由于到达黄山游客集散中心已是14:00,由于距离黄山还有10多公里,你可以走路去黄山山脚下,而且16:00之后没有大巴去黄山景区。因此随便找了个地吃完中午饭,就往乘大巴车黄山景区去了(¥12/人),由于上山的入口有好几个,我们也没有去研究,大巴到 云谷寺 景区,我们也就下车从这里出发往山上去了,你可以坐缆车去往山顶,我们一行三人,选择了徒步上山,对了门票:¥230/人

-

一路说说笑笑,也没有预订上山的旅店,我们心真大,刚走了没多久,就看到了两个人被交椅抬着下山了,其中一个应该是摔了,头破血流的样子,还没开始,就…;没多管,一路还是很轻松,毕竟都是年轻人,体力不错,走到 白鹅岭 已经开始下雨,雨越下越大,因为在边走边看的路上,我们决定来黄山当然是去 迎客松 的景点,然后我们顺着 白鹅岭 前往 白鹅山庄旅游商场 去避雨,然后是人多的无法挪开脚,此时天色已晚,我们稍作休息,找了半天也没有能睡得地,那床都是人挤人。我们找了个茶馆,吃了些带着的食品,喝了一小时茶,大约20:00左右,我们决定,今晚夜行到 迎客松

-

雨后起了大雾,山顶那时雾色正浓,能见度大约在3米。我们三人也紧随其形,在 光明顶 片区玩了一会,这里看日出不错,当我们并没有这里等日出,毕竟这里离 迎客松 有一小时多的行程,我们要明天早早的在 迎客松 那里拍照装逼,拍完照然后回走去最高峰 莲花峰 ,然而到了 迎客松 才发现,并不像电视上看到的,是在山的悬崖边。好了,这会才23:00多,怎么办,还有好几小时,又没有帐篷什么地可住,三人就在这 迎客松 前的广场上,发现了超大遮阳伞两把,哈哈哈,我们就用遮阳伞前后堵住,加上自己的雨伞,构建了一个堡垒,这下,我们三可用在里面睡觉了,雨后的山上很潮湿,就这样半将半究的,坚持到4点多。

-

天快要亮了,要找地儿去拍日出,我答应别人了,要发日出照片给她,往回走去 莲花峰 那里并不合适,更重要的是山路也被封,不上上去,只好找到 玉屏索道 的另一条路上,这里刚刚好可用看到日出

-

日出

-

拍完日出,我们快速折回到 迎客松 ,那里已经开始有三三两两的人了,我们动作要快,否则等会从索道上来大批人马,嗯,快速装逼完成,迅速撤离战场

-

迎客松

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/02/hexo-iterative/index.html b/2018/05/02/hexo-iterative/index.html deleted file mode 100644 index 1ae707bca..000000000 --- a/2018/05/02/hexo-iterative/index.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Hexo Blog 迭代 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Hexo Blog 迭代 -

- - -
- - - - -

最初博客通过Cloudflare反向代理进行HTTPS解析,放完五一假期,Github官方开始支持自定义域名的HTTPS解析,在使用Cloudflare期间,经常性的521等问题烦恼,这次也可以名正言顺的弃用CloudFlare

-

本次迭代内容

-
    -
  • 弃用Cloudflare
  • -
  • 自动化部署
  • -
  • 常用设置
  • -
  • 常用插件安装
  • -
- -

弃用Cloudflare

-
    -
  1. 关闭Cloudflare中设置Page Rules
  2. -
  3. 删除Cloudflare的DNS记录
  4. -
  5. 还原域名配置中的DNS解析
  6. -
  7. 添加Github提供的IP解析
  8. -
-

官方自定义域名设置

-

自动化部署

-
-

Github Pages是Github 提供一个渲染静态的Web页面服务

-
-
    -
  • {username}.github.io仓库默认master分支
  • -
  • 其他项目仓库,默认gh-pages分支
  • -
  • 官方说明文档
  • -
-

因此{username}.github.io仓库,dev分支用来存储网站的源码,master分支存放生成的静态文件,这样一个仓库就可以管理整个项目。每次push新的功能,然而每次都需要先pushdev分支,然后生成静态文件,再pushmaster分支,这种重复性的操作,实在太不优雅,所以采用Travis CI进行自动化部署

-

接着Github支持自定义域名开启HTTPS的好消息,Travis CI (https://travis-ci.com) 也支持开源项目啦

-
-

Travis CI 区别

-
-
    -
  • Travis-CI(https://travis-ci.org) :GitHub公开项目
  • -
  • Travis-CI(https://travis-ci.com) :私有付费项目2018.05.02也开始支持开源项目
  • -
-

GitHub Services are being deprecated,因此本节的自动化部署就开启Travis CI (https://travis-ci.com) 集成方案

-

准备

-
    -
  1. 使用GitHub账号登录Travis-CI,并确认接受访问
  2. -
  3. 同步了GitHub存储库,转到您的配置文件页面并启用您想要构建的存储库
  4. -
  5. 添加 .travis.yml 文件到构建部署项目的根目录下
  6. -
-

Hexo 自动部署

-

部署流程
-部署流程

-

Hexo 部署脚本示例

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 设置语言
language: node_js
# 设置相应的版本
node_js:
- '12.16.3'
# - lts/*
# 可以减少travis构建时间
cache:
directories:
- node_modules
before_install:
# - npm config set bin-links false
# - npm install -g hexo
- npm install -g hexo-cli
# 安装hexo及插件
install:
- npm install
before_script:
- npm install -g mocha
- git clone --branch master https://github.com/BladeCode/BladeCode.github.io.git public
script:
# 清除
- hexo cl
# 生成
- hexo g
after_script:
- cd ./public
- git init
# 修改成自己的github用户名
- git config user.name "BladeCode"
# 修改成自己的GitHub邮箱
- git config user.email "Jerry.x@outlook.com"
- git add .
- git commit -m "update by Travis-CI on `date '+%Y-%m-%d %H:%M:%S'`"
# GH_token就是在travis中设置的token
- git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:master
branches:
only:
# 只监测这个分支,一有动静就开始构建
- dev
env:
global:
# 设置仓库地址
- GH_REF: github.com/BladeCode/BladeCode.github.io.git

-

常用设置

-

NexT 配置使用手册
-NexT 配置使用手册

-

NexT主题更新

-

官方说明

-

常用插件安装

-
    -
  • 文章字符统计 hexo-symbols-count-time
  • -
  • 修复LeanCloud访客计数器中的严重安全漏洞 hexo-leancloud-counter-security
  • -
  • 图片灯箱 theme-next-fancybox3
  • -
  • 本地检索 hexo-generator-searchdb
  • -
  • 注脚 hexo-renderer-markdown-it-plus
  • -
  • 文章加密 hexo-blog-encrypt
  • -
-

其他

-

图床选择

-
    -
  • 个人网站中的静态文件云存储选择
  • -
  • 嗯,图片就交给它了
  • -
  • NexT主题无法备份解决方式
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/15/linux-build/index.html b/2018/05/15/linux-build/index.html deleted file mode 100644 index 0528792a5..000000000 --- a/2018/05/15/linux-build/index.html +++ /dev/null @@ -1,783 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Linux 常用应用安装 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Linux 常用应用安装 -

- - -
- - - - -

作为 Android 开发者,目标主要是在客户端,平时也就是和服务端对接数据接口,很少直接干到服务端的 Linux 机器,随着这波推动团队技术平台基础开发工具模块的完善,拿到了一台 Linux 机器,重新构建移动端的测试服务器。

-

该机器主要功能:

-
    -
  1. 提供移动端服务 Api 接口
  2. -
  3. 提供移动端通讯录管理授权服务
  4. -
  5. 提供企业微信通讯录同步服务
  6. -
  7. 管理移动端服务器 Api 接口文档
  8. -
- -

也是第一次正式的从头开始安装所需软件及应用部署,虽然这些工作可以完全找运维去处理,难得这样的机会从头开始去熟悉 Linux。

-

安卓,是一个基于Linux内核的开放源代码移动操作系统,因此多了解 Linux 是一件双赢的事情,基于当前机器需要提供的服务,安装部署需要的软件应用

-
废话不多说,上来就是干
-

和 Windows 一样不同的系统,安装的软件也是有区别的,而且 Linux 的系统众多,因此需要先查看系统的版本及相关信息,然后再下载对应系统版本的应用进行安装

-

查看 Linux 发行版的名称及其版本号

-

查看内核

-
    -
  1. cat /proc/version
    1
    2
    [dc2-user@10-255-0-191 ~]$ cat /proc/version
    Linux version 3.10.0-957.27.2.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Mon Jul 29 17:46:05 UTC 2019
    -
  2. -
  3. uname -a
    1
    2
    [dc2-user@10-255-0-191 ~]$ uname -a
    Linux 10-255-0-191 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
    -
  4. -
-

查看 Linux 系统版本

-
    -
  1. lsb_release -a:列出所有版本信息
    1
    2
    3
    4
    5
    6
    [dc2-user@10-255-0-191 ~]$ lsb_release -a
    LSB Version: :core-4.1-amd64:core-4.1-noarch
    Distributor ID: CentOS
    Description: CentOS Linux release 7.6.1810 (Core)
    Release: 7.6.1810
    Codename: Core
    -
  2. -
  3. cat /etc/redhat-release:只适合 Redhat 系的 Linux
    1
    2
    [dc2-user@10-255-0-191 ~]$ cat /etc/redhat-release
    CentOS Linux release 7.6.1810 (Core)
    -
  4. -
  5. cat /etc/issue:此命令也适用于所有的 Linux 发行版
    1
    2
    3
    [root@localhost ~]# cat /etc/issue
    CentOS release 6.7 (Final)
    Kernel \r on an \m
    -
  6. -
-

Java

-

官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.rpm,.gz两种格式安装包

-
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 下载安装包
# 拷贝安装包到需要安装的服务器
# 2. 解压并安装
# .rpm 格式安装(jdk-xxx.rpm更换成对应的文件名)
sudo rpm -ivh jdk-xxx.rpm
# .gz 格式安装(解压到指定目录,常存放 /usr/java/ 路径)
tar zxvf jdk-xxx.tar.gz -C /usr/java/
# 3. 设置环境变量
vim /etc/profile
# 指定 JDK 的配置信息(修改这里路径,指向 jdk 安装路径)
JAVA_HOME=/usr/java/jdk1.8.0_172
PATH=$JAVA_HOME/bin:$PATH
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME PATH CLASSPATH
# 4. 编译配置文件,使修改生效
source /etc/profile
# 5. 验证 jdk 是否安装成功
java –version
-

Tomcat

-

官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.zip,.gz两种格式安装包,Linux服务器下载Core类即可

-
-
1
2
3
4
5
6
7
8
9
# 1. 下载安装文件
wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-9/v9.0.8/bin/apache-tomcat-9.0.8.tar.gz
# 2. 解压安装文件(解压到指定目录,常存放 /usr/tomcat/ 路径)
tar -zxvf apache-tomcat-9.0.8.tar.gz -C /usr/tomcat/
# 3. 启动 tomcat
cd /usr/local/tomcat/bin
./startup.sh
# 4. 关闭 tomcat
./shutdown.sh
-

配置Web管理账号

-
    -
  • 修改文件 conf/tomcat-users.xml,在元素中添加帐号密码,需要指定角色
    1
    2
    3
    4
    vim /usr/local/tomcat/conf/tomcat-users.xml
    # <tomcat-users>
    # <user name="admin" password="admin" roles="admin-gui,manager-gui" />
    # </tomcat-users>
    -
  • -
-

配置端口

-
    -
  • 可以修改 conf 目录下的文件 server.xml,修改 Connector 元素(Tomcat 的默认端口是 8080),需要重新启动 Tomcat 服务生效
    1
    2
    vim /usr/local/tomcat/conf/server.xml
    # <Connector port="9999" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
    -
  • -
-

应用部署

-
    -
  • 放置需部署包到容器中 webapps 路径
    1
    cd /usr/local/tomcat/webapps
    -
  • -
  • 启动服务
    1
    2
    cd /usr/local/tomcat/bin
    ./startup.sh
    -
  • -
-

Maven

-

官方网站,选择需要的版本下载

-
    -
  • 官方 Maven: https://maven.apache.org/
  • -
  • Maven 下载地址: https://maven.apache.org/download.cgi
  • -
  • Maven 历史版本: https://archive.apache.org/dist/maven/maven-3/
  • -
-

安装

-
1
2
3
4
5
6
# 下载文件 Maven 文件
wget https://mirrors.bfsu.edu.cn/apache/maven/maven-3/3.8.1/binaries/apache-maven-3.8.1-bin.tar.gz
# 解压 Maven 文件
tar -xvf apache-maven-3.8.1-bin.tar.gz
# 移动 Maven 文件到 /usr/local/ 路径
sudo mv -f apache-maven-3.8.1 /usr/local/
-

配置

-
1
2
3
4
5
6
7
# 编辑环境配置
vim /etc/profile
# 添加如下环境变量,文件末尾添加如下代码
export MAVEN_HOME=/usr/local/apache-maven-3.8.1
export PATH=${PATH}:${MAVEN_HOME}/bin
# 使环境变量生效
source /etc/profile
-

验证

-
1
mvn -v
-

如果需要更改 Maven 的镜像源,可参考 专治各种网络不服 文章

-

Apache

-

一般系统中已经包含 Apache 应用
-官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.bz2,.gz两种格式安装包

-
-

安装

-

查看系统中是否已包含 httpd 应用

-
1
2
3
rpm -qa | grep httpd
# 或
yum list | grep httpd
-
    -
  • -

    方式一

    -
    1
    2
    3
    4
    # 1. 下载需要的版本文件
    wget http://apache.claz.org//httpd/httpd-2.4.33.tar.gz
    # 2. 解压安装文件(解压到指定目录,常存放 /usr/local/httpd/ 路径)
    tar -zxvf httpd-2.4.33.tar.gz -C /usr/local/httpd/
    -
  • -
  • -

    方式二(推荐)

    -
    1
    2
    # 1.下载安装 httpd
    yum install httpd
    -
  • -
-

卸载

-
1
2
3
yum erase httpd.x86_64
# 或
rpm -e httpd.x86_64
-

常用命令

-
1
2
3
4
5
6
# 查看服务运行状态
systemctl status httpd.service
# 启动 apache 服务
systemctl start httpd.service
# 停止 apache 服务
systemctl stop httpd.service
-

RPM默认安装路径:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
路径说明
/etc一些设置文件放置的目录如 /etc/crontab
/usr/bin一些可执行文件
/usr/lib一些程序使用的动态函数库
/usr/share/doc一些基本的软件使用手册与帮助文档
/usr/share/man一些 man page 文件
-

Nginx

-

官方下载地址,选择需要的版本下载安装包(最新安装版本 1.14.0)

-
-

官方提供了.zip,.gz两种格式安装包

-
-

安装

-
    -
  • -

    方式一

    -
    1
    2
    3
    4
    5
    6
    7
    # 1. 下载安装文件
    wget http://nginx.org/download/nginx-1.14.0.tar.gz
    # 2. 解压安装文件(解压到指定目录,常存放 /usr/local/ 路径)
    tar -zxvf nginx-1.14.0.tar.gz -C /usr/local/
    # 3. 编译安装依赖库
    cd /usr/local/nginx/
    ./configure
    -
  • -
  • -

    方式二

    -
    1
    2
    # 默认安装路径 /etc/nginx/
    yum install nginx
    -
  • -
-

常用命令

-
    -
  • 加压文件安装常用命令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 停止 ngix
    /usr/local/nginx/sbin/nginx -s quit
    # 重新载入 nginx(当配置信息发生修改时)
    /usr/local/nginx/sbin/nginx -s reload
    # 查看版本
    /usr/local/nginx/sbin/nginx -v
    # 查看 nginx 的配置文件的目录
    /usr/local/nginx/sbin/nginx -t
    # 查看帮助信息
    /usr/local/nginx/sbin/nginx -h
    -
  • -
  • yum安装常用命令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 启动
    systemctl start nginx
    # 停止
    systemctl stop nginx
    # 重启
    systemctl restart nginx
    # 查看运行状态
    systemctl status nginx
    # 开机启动
    systemctl enable nginx
    -
  • -
-

Node

-

官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.gz,.7z,zip等多种格式安装包

-
-

安装

-
1
2
3
4
5
6
7
# 1. 下载安装文件
wget https://nodejs.org/download/chakracore-release/v8.6.0/node-v8.6.0-linux-x64.tar.gz
# 2. 解压安装文件(解压到当前目录)
tar -zxf node-v8.6.0-linux-x64.tar.gz
# 3. 建立软链接,实现全局访问
ln -s /root/node-v8.6.0-linux-x64/bin/node /usr/local/bin/node
ln -s /root/node-v8.6.0-linux-x64/bin/npm /usr/local/bin/npm
-

Redis

-

官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.gz格式安装包

-
-

安装

-
1
2
3
4
5
6
7
8
9
10
# 1. 下载安装文件
wget wget http://download.redis.io/releases/redis-4.0.10.tar.gz
# 2. 解压安装文件(解压到当前目录)
tar xzf redis-4.0.10.tar.gz
# 3. 编译安装
cd redis-4.0.10
# 编译
make
# 4. 启动服务
src/redis-server
-

配置

-
1
2
# 修改 redis.conf 文件中 daemonize 属性 为 yes
vim /you_install_path/redis.conf
-
-

其他配置根据自身需要调整修改

-
-

其他命令

-
    -
  1. 关闭服务
    1
    redis-cli -h 127.0.0.1 -p 6379 shutdown
    -
  2. -
  3. 非安全模式启动
    1
    2
    # 后台以非安全模式启动
    nohup /usr/local/bin/redis-server --protected-mode no &
    -
  4. -
-

常用命令

-

文件查找

-

find

-

find 命令是根据文件的属性进行查找,如文件名,文件大小,所有者,所属组,是否为空,访问时间,修改时间等。

-
    -
  • 基本格式:
    -find path expression
  • -
  • 示例: -
      -
    • 在根目录下查找文件 httpd.conf,表示在整个硬盘查找
      -find / -name httpd.conf
    • -
    • 表示当前目录下查找文件名开头是字符串 srm 的文件
      -find . -name ‘srm*’
    • -
    • 查找在系统中最后 10 分钟访问的文件(access time)
      -find / -amin -10
    • -
    • 查找在系统中属于 fred 这个用户的文件
      -find / -user fred
    • -
    • 查找出小于 1000KB 的文件
      -find / -size -1000k
    • -
    -
  • -
-

grep

-

grep 是根据文件的内容进行查找,会对文件的每一行按照给定的模式(patter)进行匹配查找。

-
    -
  • 基本格式:
    -find expression
  • -
  • 主要参数:
    --c:只输出匹配行的计数。
    --i:不区分大小写
    --h:查询多文件时不显示文件名。
    --l:查询多文件时只输出包含匹配字符的文件名。
    --n:显示匹配行及行号。
    --s:不显示不存在或无匹配文本的错误信息。
    --v:显示不包含匹配文本的所有行。
  • -
  • 示例: -
      -
    • 显示所有包含每行字符串至少有 5 个连续小写字符的字符串的行
      -grep ‘[a-z]{5}’ aa
    • -
    • 显示所有以 d 开头的文件中包含 test 的行
      -grep ‘test’ d*
    • -
    -
  • -
-

进程相关

-
    -
  • -

    查看指定服务进程

    -
    1
    2
    3
    4
    # 查看 httpd 服务进程
    ps -ef | grep httpd
    # UID PID PPID C STIME TTY TIME CMD
    # root 7192 7103 0 19:59 pts/3 00:00:00 grep --color=auto httpd
    -
      -
    • UID:用户 ID
    • -
    • PID:进程 ID
    • -
    • PPID:父进程 ID
    • -
    • C:CPU 用于计算执行优先级的因子。数值越大,表明进程是 CPU 密集型运算,执行优先级会降低;数值越小,表明进程是 I/O 密集型运算,执行优先级会提高
    • -
    • STIME:进程启动的时间
    • -
    • TTY:完整的终端名称
    • -
    • TIME:CPU 时间
    • -
    • CMD:完整的启动进程所用的命令和参数
    • -
    -
    -
  • -
  • -

    杀死指定进程

    -
    1
    kill -9 pid(逐个都删除)
    -
  • -
  • -

    查看指定端口

    -
    1
    2
    # 检测 6379 端口是否在监听  
    netstat -lntp | grep 6379
    -
  • -
-

文件复制

-

语法

-
1
scp(选项)(参数)
-

选项

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-1:使用 ssh 协议版本 1;
-2:使用 ssh 协议版本 2;
-4:使用 ipv4;
-6:使用 ipv6;
-B:以批处理模式运行;
-C:使用压缩;
-F:指定ssh配置文件;
-i:identity_file 从指定文件中读取传输时使用的密钥文件(例如亚马逊云 pem),此参数直接传递给ssh;
-l:指定宽带限制;
-o:指定使用的 ssh 选项;
-P:指定远程主机的端口号;
-p:保留文件的最后修改时间,最后访问时间和权限模式;
-q:不显示复制进度;
-r:以递归方式复制。
-

参数

-
    -
  • 源文件:指定要复制的源文件
  • -
  • 目标文件:目标文件。格式为 user@host:filename(文件名为目标文件的名称)
  • -
-

示例

-
    -
  • -

    上传本地文件到远程机器指定目录

    -
    1
    2
    3
    scp /opt/soft/nginx-0.5.38.tar.gz root@10.10.10.10:/opt/soft/scptest
    # 指定端口 2222
    scp -rp -P 2222 /opt/soft/nginx-0.5.38.tar.gz root@10.10.10.10:/opt/soft/scptest
    -
  • -
  • -

    上传本地目录到远程机器指定目录

    -
    1
    scp -r /opt/soft/mongodb root@10.10.10.10:/opt/soft/scptest
    -
  • -
  • -

    从远程机器复制文件到本地

    -
    1
    scp root@10.10.10.10:/opt/soft/nginx-0.5.38.tar.gz /opt/soft/
    -
  • -
  • -

    从远程机器复制文件(含目录)到本地

    -
    1
    scp -r root@10.10.10.10:/opt/soft/mongodb /opt/soft/
    -
  • -
-

文件删除

-

语法

-
1
rm [选项] 文件或目录
-

选项

-
1
2
3
4
-f:强行删除,忽略不存在的文件,不提示确认。(f 为 force 的意思)
-i:进行交互式删除,即删除时会提示确认。(i 为 interactive 的意思)
-r:将参数中列出的全部目录和子目录进行递归删除。(r 为 recursive 的意思)
-v:详细显示删除操作进行的步骤。(v 为 verbose 的意思)
-

示例

-
    -
  • -

    删除一个文件

    -
    1
    rm file
    -
  • -
  • -

    删除一个目录

    -
    1
    rm file/
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/17/git-sub/index.html b/2018/05/17/git-sub/index.html deleted file mode 100644 index c99cbbc05..000000000 --- a/2018/05/17/git-sub/index.html +++ /dev/null @@ -1,607 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Git 子仓库管理 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Git 子仓库管理 -

- - -
- - - - -

在使用 NexT 作为 Hexo 博客的主题时,不能 友好 的支持其主题的更新,以及 多设备 之间的主题同步。

-

按照官方提供的导入主题操作指引

-
1
2
$ cd hexo
$ git clone https://github.com/theme-next/hexo-theme-next themes/next
-

发现commit并push到GitHub的远程服务器上,发现 themes/next 路径下并不能打开和查看该路径下的文件,原因是NexT是当前项目的一个子仓库(项目),在 Github 上对于之仓库项目的引用,推荐使用 git subtree 命令来进行对子仓库的管理,不推荐直接拷贝需要子仓库的代码到自己的项目中

- -

原因是我是使用 Travis CI 来部署自己的项目,具体的构建脚本和介绍请看,下面分别使用 git submodulegit subtree 的方式进行 NexT 主题的管理

-

git submodule 与 git subtree

-

git submodulegit subtree都可以实现一个仓库作为其他仓库的子仓库的管理

-
-
    -
  • git submodule:是 Git 官方以前的推荐方案
  • -
  • git subtree:Git 1.5.2 开始,Git 新增并推荐使用这个功能来管理子项目
  • -
  • git subtreegit submodule不同,它不增加任何像 .gitmodule 这样的新的元数据文件
  • -
  • git subtree对于项目中的其他成员透明,意味着可以不知道 git subtree 的存在
  • -
-

git submodule 常用操作

-

Git Submodule功能官方操作指引

-

add 一个submodule

-
    -
  1. -

    Fork Repository
    -hexo-theme-next项目右上角 Fork 按钮即可

    -
  2. -
  3. -

    Clone Repository

    -
    1
    git clone git@github.com:RootCluster/hexo-theme-test.git
    -
  4. -
  5. -

    Add Submodule

    -
    1
    2
    3
    4
    # 进入项目
    cd hexo-theme-test
    # 注册 next 项目是一个submodule,并把数据拷贝到 `themes/next` 路径
    git submodule add git@github.com:RootCluster/hexo-theme-next.git themes/next
    -
  6. -
  7. -

    status

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 当前 submodule 已被注册并指向了某个 commit
    git submodule status
    1f5643061ec5257269673bd6159403c24015c53d themes/next (v6.3.0)
    # 查看在父仓库中有哪些变化被注册
    git status
    On branch submodule
    Changes to be committed:
    (use "git reset HEAD <file>..." to unstage)
    new file: .gitmodules
    new file: themes/next
    -
    -

    有2个文件被修改过:.gitmodules,themes/next,当在父仓库时,Git 不会跟踪 submodule 中的文件,Git 只把它当成一个单一的文件

    -
    -
      -
    • .gitmodules:存有 submodule 的信息
    • -
    • themes/next:submodule 它自己
    • -
    -
  8. -
  9. -

    commint

    -
    1
    2
    3
    4
    5
    6
    # 推送到远程 submodule 分支
    git commit -am "add next submodule"
    [submodule a5a612b] add next submodule
    2 files changed, 4 insertions(+)
    create mode 100644 .gitmodules
    create mode 160000 themes/next
    -
  10. -
  11. -

    push

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    git push origin submodule
    Counting objects: 4, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (4/4), done.
    Writing objects: 100% (4/4), 451 bytes | 451.00 KiB/s, done.
    Total 4 (delta 1), reused 0 (delta 0)
    remote: Resolving deltas: 100% (1/1), completed with 1 local object.
    To github.com:RootCluster/hexo-themes-test.git
    71879a8..a5a612b submodule -> submodule
    -
  12. -
-

查看 Github 上的仓库,发现父仓库里有一个指向 submodule 的链接,表示你已经成功添加了一个 submodule

-

clone 带 submodule 的项目

-

新路径下,clone 项目,submodule 分支

-
1
2
3
4
5
6
7
8
9
# clone 项目
git clone -b submodule git@github.com:RootCluster/hexo-themes-test.git
# 进入项目路径
cd hexo-themes-test/
# 项目注册 submodule
git submodule init
# clone submodule 代码
git submodule update

-

update 带 submodule 的项目

-

只要在 submodule 路径下,所有的常规Git操作,如 push, pull, reset, status 等,都可以正常工作,如果要保证 submodule 和远程仓库保存同步,在 submodule 路径下运行 git pull

-
    -
  • 如果你得到一个错误信息, 说你不在任何分支之上, 只要运行 git checkout master 就可修复
  • -
  • 如果你在 pullsubmodule 有一些更新, 父仓库会告诉你有一些变动需要 commit 了, submodule自身指向一个指定的 commit, 并且如果这个 commit 改变了, 父仓库会得知这个改变. 如果你的 submodule 需要在一个指定 commit 上工作, 可用 git reset 来设置
  • -
-

例如:我需要把NexT的版本改变到上一个 Tag 6.2.0 (目前是6.3.0)

-
-

git reset --hard (commit hash)

-
-
1
2
3
4
5
6
7
8
# 进入项目路径
cd hexo-themes-test/
# 重新指向 submodule 关联的 commit 记录
git reset --hard 206d463
# 回到父目录
cd ..
# commit 本次的修改
git commit -am "set next version to 6.2.0"
-

推送到远程仓库后,submodule 会和指定的commit 关联起来。如果你和别人一起工作在同一个项目,别人也可以在submodulepull并且commit,因此改变了submodulecommit指向,这个问题,可以通过git reset 来解决

-
-

remove 项目中的 submodule

-
    -
  • 项目的根目录下(不是 submodule 的目录),编辑 .gitmodules 文件,删除submodule配置
    1
    2
    3
    [submodule "themes/next"]
    path = themes/next
    url = https://github.com/RootCluster/hexo-theme-next.git
    -
  • -
  • 项目根目录下,编辑 .git 文件夹下 config 文件,删除 submodule 配置
    1
    2
    [submodule "themes/next"]
    url = https://github.com/RootCluster/hexo-theme-next.git
    -
  • -
  • 清除 submodule 缓存
    1
    git rm --cached themes/next
    -
  • -
-

git subtree 常用操作(重点)

-

add一个subtree

-
    -
  • -

    在父仓库中新增子仓库

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 添加子仓库
    git subtree add --prefix=themes/next https://github.com/RootCluster/hexo-theme-next.git master --squash
    git fetch https://github.com/RootCluster/hexo-theme-next.git master
    warning: no common commits
    remote: Counting objects: 3407, done.
    remote: Total 3407 (delta 0), reused 0 (delta 0), pack-reused 3406
    Receiving objects: 100% (3407/3407), 1.21 MiB | 36.00 KiB/s, done.
    Resolving deltas: 100% (2192/2192), done.
    From https://github.com/RootCluster/hexo-theme-next
    * branch master -> FETCH_HEAD
    Added dir 'themes/next'
    -
    -

    --squash参数表示不拉取历史信息,而只生成一条 commit 信息

    -
    -
  • -
  • -

    查看项目状态

    -
    1
    2
    3
    4
    5
    6
    7
    # 查看项目状态
    git status
    On branch subtree
    Your branch is ahead of 'origin/subtree' by 2 commits.
    (use "git push" to publish your local commits)

    nothing to commit, working tree clean
    -
  • -
  • -

    推送更改到远程仓库

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    git push origin subtree
    Counting objects: 381, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (334/334), done.
    Writing objects: 100% (381/381), 650.26 KiB | 34.22 MiB/s, done.
    Total 381 (delta 23), reused 225 (delta 19)
    remote: Resolving deltas: 100% (23/23), completed with 1 local object.
    To https://github.com/RootCluster/hexo-themes-test.git
    8ed2e2e..405af42 subtree -> subtree
    -
  • -
-

pull 子仓库更新

-
1
2
3
4
5
# 更新子仓库
git subtree pull --prefix=themes/next https://github.com/RootCluster/hexo-theme-next.git master --squash
From https://github.com/RootCluster/hexo-theme-next
* branch master -> FETCH_HEAD
Subtree is already at commit 1f5643061ec5257269673bd6159403c24015c53d.
-

push 子仓库修改

-

在引用子仓库的项目中修改了子仓库的相关代码,推送修改到源仓库

-
    -
  • commit 修改记录
  • -
  • push 到源仓库
    1
    2
    # 推送子仓库修改到源仓库master分支
    git subtree push --prefix=themes/next https://github.com/RootCluster/hexo-theme-next.git master
    -
  • -
-

subtree 常用命令

-
1
2
3
4
5
6
git subtree add   --prefix=<prefix> <commit>
git subtree add --prefix=<prefix> <repository> <ref>
git subtree pull --prefix=<prefix> <repository> <ref>
git subtree push --prefix=<prefix> <repository> <ref>
git subtree merge --prefix=<prefix> <commit>
git subtree split --prefix=<prefix> [OPTIONS] [<commit>]
-

附录

-
    -
  • 如何使用 Git Submodule
  • -
  • git subtree教程
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/20/ssm/index.html b/2018/05/20/ssm/index.html deleted file mode 100644 index 9387df470..000000000 --- a/2018/05/20/ssm/index.html +++ /dev/null @@ -1,548 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -构建基础SSM框架 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 构建基础SSM框架 -

- - -
- - - - -

SSM结构

-

SSM

- -

SSM框架整合

-

所谓的SSM即:Spring,SpringMVC,Mybatis

-
    -
  • Spring:一个轻量级的框架,有很多的拓展功能,最主要的我们一般项目使用的就是IOC和AOP。
  • -
  • SpringMVC:Spring实现的一个Web层,相当于Struts的框架,但是比Struts更加灵活和强大.
  • -
  • Mybatis:一个持久层的框架,在使用上相比Hibernate更加灵活,可以控制SQL的编写,使用 XML或注解进行相关的配置.
  • -
-

实战项目

-

ssm-practice

-

项目功能:

-
    -
  1. Spring,SpringMVC,Mybatis框架整合
  2. -
  3. Create Features
  4. -
  5. Retrieve Features
  6. -
  7. Update Features
  8. -
  9. Delete Features
  10. -
-
-

项目示例:rc-ssm

-
-

其他

-

ajax之PUT请求

-

客户端ajax方式发送PUT请求,Tomcat默认不会对请求进行处理;
-Tomcat:

-
    -
  1. 将请求体中的数据,封装成一个map
  2. -
  3. request.getParameter(“fileName”)就会从这个map中取值
  4. -
  5. springMVC封装POJO对象时,会把POJO中的属性的值,request.getParameter(“fileName”)
  6. -
-

解决方式:

-
    -
  • -

    方式一:Ajax发送POST请求
    -Ajax中type:“POST”
    -data: $(“”).serialize()+“&_method=PUT”

    -
  • -
  • -

    方式二:web配置中添加HttpPutFormContentFilter过滤器
    -1.HttpPutFormContentFilter将请求体中的数据解析包装成一个map
    -2.request被重新包装,request.getParameter()被重写,从自己封装的map中取出数据

    -
  • -
-

获取属性的值

-

prop修改和读取DOM原生属性的值
-attr修改和读取自定义属性的值

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/20/travel-zjj/index.html b/2018/05/20/travel-zjj/index.html deleted file mode 100644 index 20384a3ba..000000000 --- a/2018/05/20/travel-zjj/index.html +++ /dev/null @@ -1,592 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -行·张家界 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 行·张家界 -

- - -
- - - - -
    -
  • 时间:2018.06.16——2018.06.19
  • -
  • 地点:杭州——张家界
  • -
  • 目标:武陵源景区,天门山景区,大峡谷景区
  • -
-
-

听说张家界是人间仙境,鬼斧神工,嗯,今年端午就去一探究竟,慌慌张张,匆匆忙忙做一份旅行攻略,翻遍百度,爬烂谷歌,都没有找到匹配的攻略,哎,可能是我姿势不对?!

-

张家界,张家界景区共分为四块:张家界国家森林公园杨家界自然保护区天子山自然保护区索溪峪自然保护区四大景区,统称为武陵源风景名胜。

- -

最受欢迎 的四大景区

-
    -
  1. 武陵源景区(森林公园、金鞭溪、袁家界、杨家界、天子山、十里画廊等)
  2. -
  3. 天门山景区(亚州最长的索道、世界公路奇观、玻璃栈道等)
  4. -
  5. 大峡谷风景区(新开发的玻璃桥)
  6. -
  7. 凤凰古城
  8. -
-

出行准备

-
    -
  1. 身份证件等相关证件
  2. -
  3. 数码产品,雨具等
  4. -
  5. 简单洗漱用品及换洗衣物
  6. -
  7. 现金若干(不必太多)
  8. -
  9. 零食(必备:辣条)
  10. -
-

注意事项

-

由于是自由行的方式,因此提醒以下几点

-
    -
  1. 到达张家界后,拒绝一切 人搭话,避免一些麻烦,给行程带来不愉快
  2. -
  3. 保管好自己的物品
  4. -
  5. 张家界火车站出站后即可到汽车站乘坐大巴去武陵源景区,50分钟左右,10(森林公园)—12元(武陵源)
  6. -
  7. 大峡谷,天门山景区玻璃桥都需要提前5天在网上预定
  8. -
-

出行路线

-

整体路线图

-

路线一:

-
    -
  • Day1(16):天门山景区
  • -
  • Day2.Day3(17-18):武陵源景区
  • -
  • Day4(19):大峡谷风景区
  • -
-

路线二(推荐)

-
    -
  • Day1.Day2(16-17):武陵源景区
  • -
  • Day2.Day3(18):大峡谷风景区
  • -
  • Day4(19):天门山景区
  • -
-

扼要路线图

-

标记说明

-
    -
  1. 张家界火车站
  2. -
  3. 武陵源景区
  4. -
  5. 大峡谷风景区
  6. -
  7. 天门山景区
  8. -
-
-

武陵源景区路线

-

门票:245 元+保险费3 元(3天内多次进出有效,含环保车票价)
-开放时间:8:00-17:00
-Day1:森林公园-金鞭溪-杨家界
-Day2:大观台-天子山-十里画廊-索溪湖-武陵源门票站
-从森林公园进,从武陵源出,不走回头路。需要在 丁香榕 住一宿
-武陵源景区

-

大峡谷风景区路线

-

门票:大峡谷(门票122元)+玻璃桥(门票138元)
-开放时间:08:00-17:00
-Day3:玻璃桥-大峡谷

-

天门山景区路线

-

门票:258.00元(含往返索道、环保车)【旺季】
-开放时间:08:00~16:00
-路线Day4:玻璃栈道-天门山寺-天门洞(坐索道上山顶——走西线——再到天门翻水处坐自动扶梯到天门洞——爬999级阶梯——最终坐环保车返回至市区)
-自备中午餐

-

天门山景区路线图

-

住宿

-

现在还未确定路线,个人推荐路线二;其次,16,17,18号需要住宿,要提前预定旅店

-

美食

-

胡师傅三下锅

-

三下锅,所谓的三下锅其实就是一种很方便的干锅,它是由三种主料做成的,炖着不放汤的火锅,三角坪附近的那个“胡师傅三下锅”味道不错,三下锅50元一份,分量很够吃的,包你吃够吃好!推荐的就是干煸肠子,干煸核桃肉和湘西腊肉三种混在一起炖,吃的同时还可以点一份酸萝卜,又脆又酸。真的是极品哦!(吃过后,发现并没有网上说的这么好吃,就是大烩菜,哈哈哈)

-

等等。。。

-

汇总

-

汇总

-

游记

-

废话不说,武陵源景区不用去,虽说是5A景区,除了山还是山,而且商业气息很重,很多地方都不能步行,需要坐缆车,电梯等交通工具,况且这次去森林公园那边在修路,说是在修高铁,建议直接去 大峡谷风景区天门山景区

-

武陵源景区

-

整个武陵
-武陵源

-

大峡谷风景区

-

大峡谷

-

天门山景区

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/06/22/network-http/index.html b/2018/06/22/network-http/index.html deleted file mode 100644 index 40238cee7..000000000 --- a/2018/06/22/network-http/index.html +++ /dev/null @@ -1,704 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Http VS Https | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Http VS Https -

- - -
- - - - -

基础名称

-

请求报文

-

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:
-请求行(request line)、请求头(header)、请求内容组成,如下请求报文的一般格式。
-请求报文

- -

请求行

-
    -
  1. -

    方法:

    -
      -
    • GET: 获取资源
    • -
    • POST: 向服务器端发送数据,传输实体主体
    • -
    • PUT: 传输文件
    • -
    • HEAD: 获取报文首部
    • -
    • DELETE: 删除文件
    • -
    • OPTIONS: 询问支持的方法
    • -
    • TRACE: 追踪路径
    • -
    -
  2. -
  3. -

    URL:
    -scheme://host:port/path?query

    -
      -
    • scheme: 表示协议,如Http, Https, Ftp等
    • -
    • host: 表示所访问资源所在的主机名:如:www.baidu.com
    • -
    • port: 表示端口号,Http默认为80,Https默认为443
    • -
    • path: 表示所访问的资源在目标主机上的储存路径
    • -
    • query: 表示查询条件
    • -
    -
  4. -
  5. -

    协议/版本号:

    -
  6. -
-

请求头

-
    -
  1. 通用首部(General Header)
  2. -
  3. 请求首部(Request Header)
  4. -
  5. 实体首部(Entity Header Fields)
  6. -
-

请求内容

-

如: 客户端POST的数据就放在这里(对比:GET的数据放在请求行的URL里)

-

例如:
-请求示例

-

响应报文

-

服务端响应一个HTTP请求消息包括以下格式:
-响应行(response line)、响应头(header)、响应内容组成

-

响应行

-
    -
  1. 状态码: -
      -
    • 1XX:Informational(信息性状态码)
    • -
    • 2XX:Success(成功状态码)
    • -
    • 3XX:Redirection(重定向)
    • -
    • 4XX:Client Error(客户端错误状态码)
    • -
    • 5XX:Server Error(服务器错误状态吗)
    • -
    -
  2. -
  3. 状态码描述:
  4. -
  5. 协议/版本号:
  6. -
-

响应头

-
    -
  1. 通用首部(General Header)
  2. -
  3. 响应首部(Response Header)
  4. -
  5. 实体首部(Entity Header Fields)
  6. -
-

响应内容

-

如:服务器返回的HTML、JSON等数据

-

响应示例

-

Http

-

概念

-
    -
  • HTTP:超文本传输协议(HyperText Transfer Protocol)是一种用于分布式、协作式和超媒体信息系统的应用层协议.
  • -
  • HTTP是万维网的数据通信的基础.
  • -
-

通信

-
    -
  1. 建立TCP连接
    -在HTTP工作开始之前,Client首先要通过网络与Service建立连接,该连接是通过TCP来完成的,HTTP是比TCP更高层次的应用层协议,根据规则,只有低层协议建立之后才能进行更高层协议的连接,因此,首先要建立TCP连接
  2. -
  3. Client发起HTTP请求(Request)
    -Requset通常包含请求行,请求头,请求内容这三部风组成的请求报文
  4. -
  5. Service发送HTTP响应(Response)
    -Response通常包含响应行,响应头,响应内容这三部风组成的响应报文
  6. -
  7. Client关闭TCP连接
  8. -
-

特点

-
    -
  1. 无状态 -
      -
    • 每个请求结束后都会被关闭,每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况
    • -
    • 服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器
    • -
    -
  2. -
  3. 明文传输,可能被窃听
  4. -
  5. 不验证通信方的身份,可能遭遇伪装 -
      -
    • HTTP 协议中的请求和响应不会对通信方进行确认。也就是说存在“服务器是否就是发送请求中 URI 真正指定的主机,返回的响应是否真的返回到实际提出请求的客户端”等类似问题
    • -
    • HTTP 协议通信时,由于不存在确认通信方的处理步骤,任何人都可以发起请求
    • -
    -
  6. -
  7. 无法证明报文的完整性,可能遭遇篡改 -
      -
    • 在请求或响应送出之后直到对方接收之前的这段时间内,即使请求或响应的内容遭到篡改,也没有办法获悉
    • -
    -
  8. -
-

Https

-

概念

-
    -
  • HTTPS:超文本传输安全协议(Hypertext Transfer Protocol Secure,常称为HTTP over TLS,HTTP over SSL或HTTP Secure)是一种通过计算机网络进行安全通信的传输协议.
  • -
  • HTTPS经由HTTP进行通信,但利用SSL/TLS来加密数据包.
  • -
-
-

HTTP+加密+认证+完整性保护 = HTTPS

-
-

HTTP VS HTTPS

-

通信

-

SSL/TLS

-

SSL/TLS:安全传输层协议(Transport Layer Security), 是介于TCP和HTTP之间的一层安全协议,TLS的前身是SSL(Secure Sockets Layer)

-
-

TLS/SSL关系

-
-
    -
  • SSL2.0
  • -
  • SSL3.0
  • -
  • TLS1.0(SSL3.1)
  • -
  • TLS1.1(SSL3.2)
  • -
  • TLS1.2(SSL3.3)
  • -
-

SSL/TLS工作原理

-

HTTPS协议的主要功能都依赖于SSL/TLS协议,SSL/TLS的功能实现主要依赖于三类算法:对称加密,非对称加密,散列函数Hash

-
    -
  • 非对称加密实现身份认证和密钥协商,
  • -
  • 对称加密算法采用协商的密钥对数据加密,
  • -
  • 基于散列函数验证信息的完整性
  • -
-

SSL/TLS协议实现

-

TLS以记录协议(record protocol)实现。记录协议负责在传输连接上交换所有的底层消息,并可以配置加密。每一条TLS记录以一个短标头起始。标头包含记录内容的类型(或子协议)、协议版本和长度

-

TLS的主规格说明书定义了四个核心子协议:

-
    -
  • 握手协议(handshake protocol);
  • -
  • 密钥规格变更协议(change cipher spec protocol);
  • -
  • 应用数据协议(application data protocol);
  • -
  • 警报协议(alert protocol);
  • -
-

握手协议

-

握手是TLS协议中最精密复杂的部分。在这个过程中,通信双方协商连接参数,并且完成身份验证。根据使用的功能的不同,整个过程通常需要交换6~10条消息。根据配置和支持的协议扩展的不同,交换过程可能有许多变种,在使用中经常可以观察到以下三种流程:

-
    -
  • 单向验证(完整的握手,对服务器进行身份验证)
  • -
  • 双向验证(对客户端和服务器都进行身份验证的握手)
  • -
  • 简短握手(恢复之前的会话)
  • -
-
单向验证
-

单向验证

-
    -
  1. Handshake:ClentHello
    -客户端通过发送 Client Hello 报文开始 SSL通信。报文中包含客户端支持的 SSL的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。
  2. -
  3. Handshake:ServerHello
    -服务器可进行 SSL通信时,会以 ServerHello 报文作为应答。和客户端一样,在报文中包含 SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
  4. -
  5. Handshake:Certificate
    -之后服务器发送 Certificate 报文。报文中包含公开密钥证书。
  6. -
  7. Handshake:ServerHelloDone
    -最后服务器发送 ServerHelloDone 报文通知客户端,最初阶段的 SSL握手协商部分结束。
  8. -
  9. Handshake:ClientKeyExchange
    -SSL第一次握手结束之后,客户端以 ClientKeyExchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-mastersecret 的随机密码串。该报文已用3 中的公开密钥进行加密。
  10. -
  11. ChangeCipherSpec
    -接着客户端继续发送 ChangeCipherSpec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
  12. -
  13. Handshake:Finished
    -客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
  14. -
  15. ChangeCipherSpec
    -服务器同样发送 ChangeCipherSpec 报文。
  16. -
  17. Handshake:Finished
    -服务器同样发送 Finished 报文。
  18. -
  19. Application Data(HTTP)
    -服务器和客户端的 Finished 报文交换完毕之后,SSL连接就算建立完成。当然,通信会受到 SSL的保护。从此处开始进行应用层协议的通信,即发送 HTTP 请求。
  20. -
  21. Application Data(HTTP)
    -应用层协议通信,即发送 HTTP 响应。
  22. -
  23. Alert:warning,close notify
    -最后由客户端断开连接。断开连接时,发送 close_notify 报文(上图做了一些省略,实际到这一步还需要发送TCP FIN报文关闭TCP链接)
  24. -
-
双向验证
-

双向验证
-同单向验证流程相比,双向验证多了如下两条消息:CertificateRequestCertificateVerify,其余流程大致相同

-
    -
  • CertificateRequest
    -CertificateRequest是TLS规定的一个可选功能,用于服务器认证客户端的身份。通过服务器要求客户端发送一个证书实现,服务器应该在ServerKeyExchange之后立即发送CertificateRequest消息
  • -
  • CertificateVerify
    -当需要做客户端认证时,客户端发送CertificateVerify消息,来证明自己确实拥有客户端证书的私钥。这条消息仅仅在客户端证书有签名能力的情况下发送
  • -
-

应用数据协议(application data protocol)

-

应用数据协议携带着应用消息,只以TLS的角度考虑的话,这些就是数据缓冲区。记录层使用当前连接安全参数对这些消息进行打包、碎片整理和加密

-

警报协议(alert protocol)

-

警报的目的是以简单的通知机制告知对端通信出现异常状况。它通常会携带close_notify异常,在连接关闭时使用,报告错误

-

附录

-
    -
  • 《图解HTTP》
  • -
  • HTTP | MDN
  • -
  • 数字证书及CA的扫盲介绍
  • -
  • HTTPS 原理浅析及其在 Android 中的使用
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/06/23/network-okhttp1/index.html b/2018/06/23/network-okhttp1/index.html deleted file mode 100644 index 37fec5493..000000000 --- a/2018/06/23/network-okhttp1/index.html +++ /dev/null @@ -1,615 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Network(一) 之OkHttp 入门 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Network(一) 之OkHttp 入门 -

- - -
- - - - -

自从Android4.4的源码中可以看到HttpURLConnection已经替换成OkHttp开始( JakeWharton曾在Twitter表示 ) ,OkHttp+Retrofit+RxJava的组合网络请求一直经久不衰,主流app的网络架构基本都是这样的组合模式,存在即合理,说明OkHttp+Retrofit+RxJava的方式确实给开发,用户体验等带来可观的优势,那么这个系列文章围绕Android的网络展开.

-

OkHttp:An HTTP & HTTP/2 client for Android and Java applications

- -
-

Android 历史网络库

-
-
    -
  • HttpClient 是 Apache 提供的HTTP网络访问接口,从一开始的时候就被引入到了Android的API中;
  • -
  • HttpURLConnection 是一种多用途, 轻量极的HTTP客户端, 提供的API比较简单, 可以容易地去使用和扩展.
  • -
-

OkHttp优势

-
    -
  • 支持HTTP/2, HTTP/2通过使用多路复用技术在一个单独的TCP连接上支持并发, 通过在一个连接上一次性发送多个请求来发送或接收数据
  • -
  • 如果HTTP/2不可用, 连接池复用技术也可以极大减少延时
  • -
  • 支持GZIP, 可以压缩下载体积
  • -
  • 响应缓存可以直接避免重复请求
  • -
  • 会从很多常用的连接问题中自动恢复
  • -
  • 如果您的服务器配置了多个IP地址, 当第一个IP连接失败的时候, OkHttp会自动尝试下一个IP
  • -
  • OkHttp还处理了代理服务器问题和SSL握手失败问题,等等…
  • -
-

基本使用

-

该系列版本说明

-
    -
  • OkHttp版本统一:3.10.0
  • -
  • JDK:1.8+
  • -
-

Gradle包导入

-
1
2
3
4
// okhttp核心库
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
// okhttp网络请求拦截日志库
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
-
-

关于网络请求
-基本网络请求由请求(请求行请求头请求内容),响应(响应行响应头响应内容)两大部分组成,具体的内容请查看Http VS Https这篇文章

-
-

OkHttp请求

-

已在Http VS Https文章中介绍了,HTTP请求相关内容

-

OkHttp响应

-

已在Http VS Https文章中介绍了,HTTP响应相关内容

-

同步与异步

-

网络请求执行方式为:同步与异步;同步异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)

-

同步

-

就是在发出一个 调用 时,在没有得到结果之前,该 调用 就不返回,但是一旦调用返回,就得到返回值了。
-换句话说,就是由 调用者 主动等待这个 调用 的结果。
-Okhttp同步(execute()):Invokes the request immediately, and blocks until the response can be processed or is in error.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
String url = "https://api.github.com/users/BladeCode";
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
// 执行同步操作
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
-

异步

-

异步 则与同步相反,调用 在发出之后,这个调用就直接返回了,所以没有返回结果。
-换句话说,当一个异步过程调用发出后,调用者 不会立刻得到结果。而是在 调用 发出后,被调用者 通过状态、通知来通知 调用者,或通过回调函数处理这个调用。
-Okhttp同步(enqueue(Callback responseCallback)):Schedules the request to be executed at some point in the future.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String url = "https://api.github.com/users/BladeCode";
OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder().url(url).build();
// 返回response 对象
Response response = client.newCall(request).enqueue(new Callback() {

@Override
public void onFailure(Call call, IOException e) {
System.out.println(e.toString());
}

@Override
public void onResponse(Call call, Response response) throws IOException {
// 字符串形式表达响应
System.out.println(response.body().string());
// 或流的形式表达响应
System.out.println(response.body().charStream());
System.out.println(response.body().byteStream());
}
});
-
-

注意:

-
-
    -
  • 响应体太大(超过1MB), 应避免使用 string()方法, 因为它会将把整个文档加载到内存中.
  • -
  • 对于超过1MB的响应body, 应使用流的方式来处理响应body. 这和我们处理xml文档的逻辑是一致的, 小文件可以载入内存树状解析, 大文件就必须流式解析
  • -
-

OkHttp Get

-
1
2
3
4
5
6
7
8
9
10
11
12
13
String url = "https://api.github.com/users/BladeCode";
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();

if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
-

OkHttp Post

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();

Response response = client.newCall(request).execute();

if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}

}
-

Posting a String

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
String postBody = ""
+ "Releases\n"
+ "--------\n"
+ "\n"
+ " * _1.0_ May 6, 2013\n"
+ " * _1.1_ June 15, 2013\n"
+ " * _1.2_ August 11, 2013\n";

Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-
-

注意:当提交数据大于1MB,请使用流的方式

-
-

Post Streaming

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
RequestBody requestBody = new RequestBody() {
@Override
public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}

private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + " × " + i;
}
return Integer.toString(n);
}
};

Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-

Posting a File

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
File file = new File("README.md");

Request request = new Request.Builder()
.url("https://api.github.com/BladeCode/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-

Posting form parameters

-

使用FormEncodingBuilder来构建和HTML

标签相同效果的请求体。键值对将使用一种HTML兼容形式的URL编码来进行编码

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
RequestBody formBody = new FormEncodingBuilder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-

Posting a multipart request

-

MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-LengthContent-Type可用的话,他们会被自动添加到请求头中。

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();

Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
- -

通常,HTTP headers 的工作方式类似于 Map<String, String>:每个字段都有一个值或没有,但是一些headers允许多个值

-
    -
  • 例如:Guava’s Multimap.
  • -
  • 例如:提供多个vary headers的HTTP响应是合法且常见的。OkHttp的API试图使用两种情况都很舒适
  • -
-

在编写请求headers时

-
    -
  • 使用 header(name, value)name 的唯一内容设置为 value。如果 name 存在现有值,则在添加新值之前将删除它。
  • -
  • 使用 addHeader(name, value) 添加 headers 不会删除已存在的 header
  • -
-

在读取headers响应时,使用 header(name) 返回最后异常出现的命名值。通常这也是唯一发生,如果没有值,则 header(name) 返回null。将所有字段的值作为列表读取,请使用 headers(name)

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println("Server: " + response.header("Server"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Vary: " + response.headers("Vary"));
}
}
-

Response Caching

-

实现缓存响应,你需要一个可以读写的缓存目录,以及缓存大小的限制。缓存目录应该是私有的,不受信任的应用程序不应该读取其内容

-

让多个缓存同时访问同一缓存目录是错误的。大多数应用程序应该只调用一次 new OkHttpClient(),使用它们的缓存配置它,并在任何地方使用相同的实例。否则,两个缓存实例将互相踩踏,破坏响应缓存,并可能导致程序奔溃

-

响应缓存使用HTTP headers进行所有的配置。你可以添加headers,如:Cache-Control: max-stale=3600,OkHttp的缓存将遵循它。你的Web服务器使用自己的响应headers配置缓存响应的时间,例如:Cache-Control: max-age=9600。有缓存headers可强制缓存响应,强制网络响应,或者强制使用条件GET验证网络响应

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

public CacheResponse(File cacheDirectory) throws Exception {
// 设置缓存大小 10 MiB
int cacheSize = 10 * 1024 * 1024;
// 实例化Cache对象
Cache cache = new Cache(cacheDirectory, cacheSize);

client = new OkHttpClient.Builder()
.cache(cache)
.build();
}

public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();

String response1Body;
try (Response response1 = client.newCall(request).execute()) {
if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);

response1Body = response1.body().string();
System.out.println("Response 1 response: " + response1);
System.out.println("Response 1 cache response: " + response1.cacheResponse());
System.out.println("Response 1 network response: " + response1.networkResponse());
}

String response2Body;
try (Response response2 = client.newCall(request).execute()) {
if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);

response2Body = response2.body().string();
System.out.println("Response 2 response: " + response2);
System.out.println("Response 2 cache response: " + response2.cacheResponse());
System.out.println("Response 2 network response: " + response2.networkResponse());
}

System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
}
-
    -
  • 要阻止响应使用缓存,请使用 CacheControl.FORCE_NETWORK
  • -
  • 要阻止它使用网络,请使用 CacheControl.FORCE_CACHE
  • -
-
-

警告:如果你使用 FORCE_CACHE 且响应需要网络,OkHttp将返回504不满意请求响应

-
-

Canceling a Call

-

使用 Call.cancel() 立即停止正在进行的请求,如果线程当前正在请求或读取响应,则它将收到 IOException。当不在需要call时,使用它来保护网络,例如,当你的用户导航离开应用程序时,同步和异步调用都可以取消

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
.build();

final long startNanos = System.nanoTime();
final Call call = client.newCall(request);

// Schedule a job to cancel the call in 1 second.
executor.schedule(new Runnable() {
@Override public void run() {
System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
call.cancel();
System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
}
}, 1, TimeUnit.SECONDS);

System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
try (Response response = call.execute()) {
System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
(System.nanoTime() - startNanos) / 1e9f, response);
} catch (IOException e) {
System.out.printf("%.2f Call failed as expected: %s%n",
(System.nanoTime() - startNanos) / 1e9f, e);
}
}
-

Timeouts

-

当无法访问时,使用超时来使call失败。网络分区可能是由于客户端连接问题,服务器可读性问题或其他任何问题时。OkHttp支持连接,读取和写入超时配置

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private final OkHttpClient client;

public ConfigureTimeouts() throws Exception {
client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}

public void run() throws Exception {
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
.build();

try (Response response = client.newCall(request).execute()) {
System.out.println("Response completed: " + response);
}
}
-

Per-call Configuration

-

所有的HTTP 客户端配置都在 OkHttpClient 中,包括代理设置,超时和缓存。当你需要修改单个调用时的配置时,请调用 OkHttpClient.newBuilder()。这将返回与原始客户端共享相同连接池,调度程序和配置的构建器(Builder

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 示例:我们发出一个请求,其中500毫秒超时,另一个请求超时3000毫秒
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
Request request = new Request.Builder()
// This URL is served with a 1 second delay.
.url("http://httpbin.org/delay/1")
.build();

// Copy to customize OkHttp for this request.
OkHttpClient client1 = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
try (Response response = client1.newCall(request).execute()) {
System.out.println("Response 1 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 1 failed: " + e);
}

// Copy to customize OkHttp for this request.
OkHttpClient client2 = client.newBuilder()
.readTimeout(3000, TimeUnit.MILLISECONDS)
.build();
try (Response response = client2.newCall(request).execute()) {
System.out.println("Response 2 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 2 failed: " + e);
}
}
-

Handling authentication

-

OkHttp可以自动重试未经身份验证的请求。如果响应为401 Not Authorized,则要求Authenticator提供凭证。实现应该构建一个包含缺少凭证的新请求。如果没有可用的凭证,则返回null以跳过重试。

-

使用 Response.challenges()来获取任何身份验证挑战的方案和领域。在完成基本挑战时,使用 Credentials.basic(username, password) 对请求header进行编码

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private final OkHttpClient client;

public Authenticate() {
client = new OkHttpClient.Builder()
.authenticator(new Authenticator() {
@Override public Request authenticate(Route route, Response response) throws IOException {
if (response.request().header("Authorization") != null) {
return null; // Give up, we've already attempted to authenticate.
}

System.out.println("Authenticating for response: " + response);
System.out.println("Challenges: " + response.challenges());
String credential = Credentials.basic("jesse", "password1");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
})
.build();
}

public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/secrets/hellosecret.txt")
.build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-

为避免在身份验证不起作用时进行多次重试,你可以在返回null以放弃,例如,你可能希望在尝试这些确切凭证时跳过重试

-
1
2
3
if (credential.equals(response.request().header("Authorization"))) {
return null; // If we already failed with these credentials, don't retry.
}
-

当你达到应用程序定义的尝试限制时,你也可以跳过重试

-
1
2
3
4
5
6
7
8
9
10
11
12
private int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}

if (responseCount(response) >= 3) {
// If we've failed 3 times, give up.
return null;
}
-

附录

-
    -
  • OkHttp Wiki
  • -
  • 怎样理解阻塞非阻塞与同步异步的区别
  • -
  • OkHttp使用教程
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/07/03/security-rsa/index.html b/2018/07/03/security-rsa/index.html deleted file mode 100644 index d57cba6a2..000000000 --- a/2018/07/03/security-rsa/index.html +++ /dev/null @@ -1,550 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -非对称加密——RSA | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 非对称加密——RSA -

- - -
- - - - -

这是常用加密技术的系列文章,主要包含非对称对称JWT三类常用技术的应用

-

RSA

-

RSA:RSA加密算法是一种 非对称 加密算法。在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。RSA就是他们三人姓氏开头字母拼在一起组成的。

- -

RSA加密解密

-

公钥 加密 私钥 解密,持有公钥(多人持有,客户端)可以对数据加密,但是只有持有私钥(一人持有,服务端)才可以解密并查看数据

-

RSA加签验签

-

私钥 加签 公钥 验签,持有私钥(一人持有,服务端)可以加签,持有公钥(多人持有,客户端)可以验签

-

RSA过程示意图

-

security-rsa

-

如上图,具体表述两个场景过程

-

结果不需加密

-

场景:返回的数据不需要加密(例如:绑定银行卡的时候)

-
    -
  • 客户端Client A发送使用服务端Serve publicKey 加密 的密文cipher A(包含用户的银行卡号,手机号等重要信息)到服务器
  • -
  • 服务器Serve 通过 Serve privateKey解密
  • -
  • 服务端业务处理完成,直接返回数据(一些普通信息,比如状态码code,提示信息msg,提示操作是成功还是失败)给客户端Client A
  • -
-

结果需加密

-

场景:返回的数据需要加密(例如:用户登录)

-
    -
  • 客户端Client B发送使用服务端Serve publicKey 加密 的密文cipher B(包含用户名和密码等重要信息)以及客户端Client BClient B publicKey到服务器
  • -
  • 服务器Serve 通过 Serve privateKey解密
  • -
  • 服务端业务处理完成,直接返回数据(一般为token,token使用客户端Client BClient B publicKey加密)给客户端Client B
  • -
  • 客户端Client B使用Client B privateKey进行 解密 获取相应的用户信息等
  • -
-

密钥对

-

在使用RSA加密解密之前,首先要生成密钥对。所谓的密钥对,指的是公钥和私钥。RSA算法的密钥可以通过两个途径生成,一是借助openssl命令终端,二是使用JDK生成。
-本篇采用JDK方式生成密钥对,openssl方式可自行尝试

-

JDK

-

Serve端密钥对

-

Client端密钥对

-
Android密钥对
-
Web密钥对
-
iOS密钥对
-

OpenSSL

-

略…

-

RSA加密

-

RSA解密

-

RSA缺点

-

虽然RSA是一种较高级别加密机制,但也存在一些缺点

-
    -
  1. 产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密。
  2. -
  3. 安全性,RSA的安全性依赖于大数的因子分解,但并没有从理论上证明破译RSA的难度与大数分解难度等价,而且密码学界多数人士倾向于因子分解不是NP问题。
  4. -
  5. 速度太慢,由于RSA的分组长度太大,为保证安全性,n 至少也要 600 bit 以上,使运算代价很高,尤其是速度较慢,较对称密码算法慢几个数量级;且随着大数分解技术的发展,这个长度还在增加,不利于数据格式的标准化。
  6. -
-

附录

-

参考学习文章

-
    -
  • 一张图了解RSA加解密与加验签
  • -
  • RSA加密解密及RSA加签验签
  • -
  • RSA加解密和加签验签
  • -
  • RSA加密解密样例
  • -
  • RSA加密解密实现
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/07/11/hugo/index.html b/2018/07/11/hugo/index.html deleted file mode 100644 index 5c8f2f88a..000000000 --- a/2018/07/11/hugo/index.html +++ /dev/null @@ -1,552 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Hugo 初体验 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Hugo 初体验 -

- - -
- - - - -

个人博客使用 Hexo 搭建,使用效果很不错,RootCluster 组织主要存放自己新技术的学习和一些Demo实验。该组织同样也可以使用Github pages服务,因此也需要给RootCluster构建一个静态页面,可用直观清晰的看自己的项目,虽然之前已使用Hexo构建,为了了解其他的静态页面构建,所以这次选择了 Hugo

-

Hugo 是世界上最快的静态网站引擎。它是用 Go(aka Golang)编写的,由 bepspf13朋友开发

- -

材料准备

-
    -
  • SystemOS:Windows 10
  • -
  • Chocolatey:Windows的包管理器
  • -
  • Hugo
  • -
-

安装

-

Chocolatey安装

-

如果已安装,跳过该步骤

-
    -
  • 使用 PowerShell.exe
    1
    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
    -
  • -
  • 使用 cmd.exe
    1
    @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
    -
  • -
-

以上两种方式,选择其一即可

-

PowerShell.exe 演示

-

hugo安装

-
1
choco install hugo -confirm
-

初始化Hugo

-
    -
  • -

    初始化hugo模板

    -
    1
    hugo new site project_name
    -
  • -
  • -

    进入项目并启动项目

    -
    1
    cd project_name && hugo serve
    -

    hugo_init

    -
  • -
  • -

    主题安装

    -

    这里选择Elate主题作为组织的网站

    -

    -
  • -
-

GitHub Action 部署

-
    -
  1. 新生成部署 key
  2. -
-
1
2
3
4
# 1. 进入本地电脑的 .ssh 文件夹 
cd .ssh/
# 2. 生成部署 key
ssh-keygen -t rsa -b 4096 -C "Jerry.x@outlook.com" -f id_rsa_deploy -N ""
-
    -
  1. 添加部署 key 到项目仓库设置中
  2. -
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the action will run.
on:
# Triggers the workflow on push or pull request events but only for the master branch
push:
branches: master
pull_request:
branches: dev

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
submodules: true
fetch-depth: 0
# Runs a single command using the runners shell
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2.3.1
with:
hugo-version: '0.61.0'
# Runs a set of commands using the runners shell
- name: Build
run: hugo --minify

- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.ACTIONS_DEPLOY_KEY }}
publish_dir: ./public
commit_message: ${{ github.event.head_commit.message }}
-

Travis

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
language: go

go:
- master # 使用最新版本

# Specify which branches to build using a safelist
# 分支白名单限制: 只有hugo分支的提交才会触发构建
branches:
only:
- dev

install:
# 安装最新的hugo
- go get -v github.com/gohugoio/hugo

script:
# 运行hugo命令
- hugo

deploy:
provider: pages # 重要,指定这是一份github pages的部署配置
skip-cleanup: true # 重要,不能省略
local-dir: public # 静态站点文件所在目录
target-branch: master # 要将静态站点文件发布到哪个分支
github-token: $GITHUB_TOKEN # 重要,$GITHUB_TOKEN是变量,需要在GitHub上申请、再到配置到Travis
keep-history: true # 是否保持target-branch分支的提交记录
on:
branch: dev # 博客源码的分支
-

参考

-
    -
  1. Hugo Documentation
  2. -
  3. Hugo setup
  4. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/07/16/treasure/index.html b/2018/07/16/treasure/index.html deleted file mode 100644 index 1827dabd2..000000000 --- a/2018/07/16/treasure/index.html +++ /dev/null @@ -1,633 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -藏经阁 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 藏经阁 -

- - -
- - - - -
-

工欲善其事,必先利其器

- -
-

记录汇总一些资源库

- -

快速满足你所需各种资源汇总
-Hi World
-创造师导航
-UI设计师导航
-Devdocs

DevTools

-

集齐宇宙IDE,可以召唤神龙::>_<::
-Jetbrains 全家桶
-Android Studio
-Xcode
-Eclipse
-Visual Studio Code
-Sublime
-PostMan
-Xshell Xftp

必备的一些辅助插件,让你效率翻倍

-

通用

-
    -
  1. Alibaba Java Coding Guidelines:阿里巴巴 Java 代码规范
  2. -
  3. GrepConsole:控制台日志自定义
  4. -
  5. GsonFormat:json 生成对应的实体 bean
  6. -
  7. GenerateAllSetter:对象中所有属性生成 set 方法
  8. -
  9. ignore:文件忽略
  10. -
  11. Lombok:简化实体 bean
  12. -
  13. Markdown Navigator:Markdown 文件支持
  14. -
  15. PlantUML integration:UML 文件支持
  16. -
  17. Translation:语言翻译
  18. -
-

Android

-
    -
  1. Android ButterKnife Zelezny:ButterKnife 相关支持
  2. -
  3. Android Material Design Icon Generator:Material icon 生成
  4. -
  5. Android Parcelable code generator:Parcelable 代码生成
  6. -
  7. Android Postfix Completion:.toast,.log 等快捷写法
  8. -
  9. Android Resource Usage Count:资源使用统计
  10. -
  11. Android Studio Prettify:模板代码生成,比如:findViewById
  12. -
  13. ADB Idea:ADB 相关命令
  14. -
  15. EventBus3 Intellij Plugin:EventBus 相关支持
  16. -
  17. LayoutFormatter:格式化 XML 布局
  18. -
  19. SQLScout:SQL 相关支持
  20. -
-

Java

-
    -
  1. Alibaba Cloud Toolkit:服务部署工具
  2. -
  3. JsonToKotlinClass:json 生成 kotlin 对象
  4. -
  5. MyBatisCodeHelperPro:mybatis 支持及 XML 代码调试
  6. -

常用的代码托管平台,设备可以坏,代码不能丢
-Github
-Gitlab
-Gitee
-Coding

装起逼来,我自己都怕

-

根据文字生成字符画

-
    -
  1. taag
  2. -
  3. ascii
  4. -
-

根据图片生成字符画

-
    -
  1. ConvertPhoto2Char
  2. -
  3. Img2Txt
  4. -
-

代码生成图片

-
    -
  1. Carbon
  2. -
-

云端应用

-
    -
  1. Uzer
  2. -
-

其他

-
    -
  1. emojicopy:表情
  2. -
  3. Processon:在线编辑流程图
  4. -
  5. Pickfrom:一站式工具平台
  6. -

开发者常用的官方网站

-

一线大厂开发者网站
-Google Developer
-Apple Developer
-Microsoft Developer
-Facebook Developer
-Twitter Developer
-Github Developer
-Baidu Developer
-Alibaba Developer
-Tencent Developer

一线大厂团队博客
-IBM 技术社区
-Netflix
-Techie Delight
-Linkedin 技术博客
-Dropbox 技术博客
-Facebook 技术博客
-淘宝中间件团队
-美团技术博客
-360技术博客
-有赞技术团队

手机厂商
-Android Developer
-iOS Developer
-Samsung Developer
-Huawei Developer
-XiaoMi Developer
-HTC Developer
-Flyme Developer
-Oppo Developer
-Vivo Developer
-360 Developer
-Smartisan Developer

应用市场
-Google Play
-App Store
-XiaoMi 应用市场
-Huawei 应用市场
-360 应用市场
-酷安市场
-应用宝

小程序
-PWA
-微信小程序
-支付宝小程序
-快应用

要折腾,来呀~
-LineageOS
-XDA
-Mokee
-MoDaCo
-机锋
-智友
-MiUi
-0pengApps

国内外云厂商
-Google Cloud
-Microsoft Azure
-Amazon Web Services
-Aliyun
-Tencent Cloud
-DiDi Cloud
-MT Cloud
-NetEase Cloud

常用镜像
-Tsinghua
-LUG
-Gradle
-Firfox

-
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/07/23/linux-mysql/index.html b/2018/07/23/linux-mysql/index.html deleted file mode 100644 index 0155a3387..000000000 --- a/2018/07/23/linux-mysql/index.html +++ /dev/null @@ -1,625 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Linux 之 MySQL | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Linux 之 MySQL -

- - -
- - - - -

之前粗略的接触了Linux的基础使用和安装,这次准备在自购的服务器上跑些应用,纯属娱乐,废话不说,上来就先仍数据库。
-数据库常用的Oracle,MySQL,SQL Server,MongoDB等,排名不分先后,自己平时接触最多的也就是MySQLMongoDB,好MySQL先来一份。

-

介绍

-

MySQL是一个开源数据库管理系统,通常作为流行的LEMP(Linux,Nginx,MySQL / MariaDB,PHP / Python / Perl)堆栈的一部分安装。它使用关系数据库和SQL(结构化查询语言)来管理其数据。

-

CentOS 7更喜欢MariaDB,它是由原始MySQL开发人员管理的MySQL分支,旨在替代MySQL。如果你在CentOS 7上运行 yum install mysql,那么安装的是MariaDB,而不是MySQL

- -

对于 MySQL 也是有好几个类别

-
    -
  1. MySQL Community Server:社区版本,开源免费,但不提供官方技术支持
  2. -
  3. MySQL Enterprise Edition:企业版本,需付费,可以试用 30 天
  4. -
  5. MySQL Cluster 集群版:开源免费。可将几个 MySQL Server 封装成一个 Server
  6. -
  7. MySQL Cluster CGE:高级集群版,需付费
  8. -
  9. MySQL Workbench:一款专为 MySQL 设计的 ER/数据库建模工具,分为两个版本 -
      -
    • MySQL Workbench OSS:社区版
    • -
    • MySQL WorkbenchSE:商用版
    • -
    -
  10. -
-

先检查服务器是否已经安装了mariadb

-
1
2
3
4
# 检查是否安装了 mariadb 客户端
rpm -qa | grep mariadb
# 检查是否安装了 mariadb 服务端
rpm -qa | grep mariadb-server
-

执行结果如下图,如果没有任何提示,则表示没有安装
-

-

执行检查命令,发现有 mariadb 服务,则需要先卸载 mariadb 相关服务

-
1
2
# mariadb 相关卸载
rpm -qa |grep mariadb |xargs yum remove -y
-

-

清单

-
    -
  • OS: CentOS 7
  • -
  • DataBase:MySQL 8.0.11
  • -
-
-

uname -a查看你 Linux 系统的信息,按照系统版本选择对应的应用

-
-
1
2
3
[dc2-user@10-255-0-191 ~]$ uname -a
Linux 10-255-0-191 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
# 这里,el7.x86_64 分别表示,el7:CentOS 7,x86_64:64 位系统
-

安装

-
-

官方下载 MySQL Community Server 地址:https://dev.mysql.com/downloads/mysql/
-清华镜像 MySQL 地址:https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/

-
-

-

在线安装(推荐方式)

-

适用于当前安装服务器可以正常互联网访问,如上图,选择方式进入MySQL Yum Repository,选择对应系统的版本,比如 mysql80-community-release-el7-3.noarch.rpm

-

-

根据自己设备网络情况选择使用的下载地址

-
    -
  • 官方地址:https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm
  • -
  • 清华镜像:https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql-8.0-community-el7-x86_64/mysql80-community-release-el7-3.noarch.rpm
  • -
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 获取官方yum源安装包 mysql80-community-release-el7-3.noarch.rpm 是根据官网提供的版本信息
wget https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm
# 2. 安装rpm包
rpm -ivh mysql80-community-release-el7-3.noarch.rpm
# 3. 安装mysql-server
yum install -y mysql-server
# 如果你需要设置不区分大小写,必须在第一次启动 mysqld 服务前设置,lower_case_table_names=1
# vim /etc/my.cnf
# 4. 启动mysqld服务
systemctl start mysqld
# 5. 查看是否成功启动
ps aux|grep mysqld
# 6. 设置mysqld服务开机自启动
systemctl enable mysqld
-
-

设置不区分大小写参数,文件地址 /etc/my.cnf

-
-

离线安装

-

适用于当前安装服务器无法互联网访问,如安装截图所示,选择 MySQL Community Server 类别,然后选择对应系统及版本进行下载,总共需要如下所示相关的应用安装包

-

按照此顺序进行安装

-
1
2
3
4
5
6
7
8
rpm -ivh mysql-community-common-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-plugins-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-compat-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-embedded-compat-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-devel-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-8.0.22-1.el7.x86_64.rpm
-

安装 mysql-community-devel-8.0.22-1.el7.x86_64.rpm 时出错,需要先执行 yum install openssl-devel 命令进行安装 openssl-devel,如下图
-

-

安装完上面的包,进行启动 MySQL 服务,并进行相应的配置

-
1
2
3
4
5
6
7
8
# 如果你需要设置不区分大小写,必须在第一次启动 mysqld 服务前设置,lower_case_table_names=1
# vim /etc/my.cnf
# 启动mysqld服务
systemctl start mysqld
# 查看是否成功启动
ps aux|grep mysqld
# 设置mysqld服务开机自启动
systemctl enable mysqld
-

整个安装设置过程如下图
-

-

配置

-

由于MySQL从5.7开始不允许在首次安装后,使用空密码进行登录,系统会随机生成一个密码以供管理员首次登录使用,这个密码记录在/var/log/mysqld.log文件中

-
1
2
3
4
5
6
7
8
9
10
# 1. 查看系统提供密码
cat /var/log/mysqld.log|grep 'A temporary password'
# 2. 使用获取到的密码登录MySQL
mysql -u root -p
# 3. 切换数据库
use mysql;
# 4. 修改root密码 your_password 替换成你自己的密码就可以了,这个密码是强密码,要求密码包含大小写字母、数字及标点符号,长度大于6
alter user 'root'@'localhost' identified by 'your_password';
# 5. 刷新修改
flush privileges;
-

默认信息

-

配置信息地址

-

默认安装配置信息:/etc/my.cnf

-

MySQL 安装路径

-
1
2
# 查看 MySQL 安装的位置
which mysqld
-

其他配置信息

-

-

连接

-

自己平时习惯使用 Navicat 进行数据库操作,因此这里进行配置链接已在云端刚刚安装的MySQL服务
-linux-mysql

-

ERROR 2003

-

通过本地的工具无法连接到服务器上的 MySQL 服务,错误提示:ERROR 2003 (HY000): Can't connect to MySQL server on '116.85.58.6' (60)

-

进行原因排查,分别通过 pingtelnet 命令来检查网络连接情况,发现 IP 是通的,端口不同,那么去看服务器是不是开启了防火墙,如果开启了防火墙,需要将 MySQL 服务的端口排除

-

对于开启了防火墙的,可以将防火墙关闭,或者设置端口白名单

-
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看防火墙状态,结果显示为running或not running
## 如果显示,-bash: firewall-cmd: command not found,则表示当前服务器没有安装防火墙
firewall-cmd --state
# 关闭防火墙firewall
systemctl stop firewalld.service
systemctl disable firewalld.service
# 关闭防火墙firewall后开启
systemctl start firewalld.service
# 开启端口
## zone -- 作用域
## add-port=80/tcp -- 添加端口,格式为:端口/通讯协议
## permanent -- 永久生效,没有此参数重启后失效
firewall-cmd --zone=public --add-port=3306/tcp --permanent
## 开启3306端口后,workbench或naivcat 就能连接到MySQL数据库了
-

-

SSH 连接 MySQL 所在的服务器,查看防火墙情况,但发现压根没有防火墙,那么此时,要去看看你的云服务器的安全组设置规则,将 3306 暴露出来,查看云服务器安全组设置
-

-

在已有安全组中添加安全规则或者新增安全组在新的安全组中添加安全规则
-

-

ERROR 1130

-

按照上图图的配置信息链接MySQL,发现错误提示:ERROR 1130: Host 'xxx.xxx.xxx.xxx' is not allowed to connect to this MySQL server

-

原因

-

不允许从远程登陆MySQL服务,只能在localhost

-

解决方法

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 切换数据库
use mysql;
# 修改user 指定的host 为 %
update user set host = '%' where user = 'root';
# 查看 user 表中的信息
select host, user from user;
# 成功修改s
+-----------+------------------+
| host | user |
+-----------+------------------+
| % | root |
| localhost | mysql.infoschema |
| localhost | mysql.session |
| localhost | mysql.sys |
+-----------+------------------+
4 rows in set (0.00 sec)
-

ERROR 2059

-

继续重试链接,错误提示:ERROR 2059: Authentication plugin 'caching_sha2_password' cannot be loaded:The specified module could not be found.

-

原因

-

MySQL 8不支持动态修改密码验证方式

-

解决方法

-
方法一
-

修改plugin默认的 caching_sha2_passwordmysql_native_password

-
1
2
3
4
5
6
7
8
# 停止mysql
systemctl stop mysqld.service
# my.cnf文件中默认有下面的语句,删除前面的#号即可,没有的话就把它添加到my.cnf中 ,默认路径`/etc/my.cnf`
default-authentication-plugin=mysql_native_password
# 切换数据库
use mysql
# 给指定用户设置密码,这里`%`是因为之前已经将远程没有特殊指定,用%代替了localhost
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'your_password';
-
方法二
-

更新你的数据库连接工具,Navicat version12+,Workbench version8+

-

BDB1507

-

BDB1507 Thread died in Berkeley DB library,由于其他操作原因造成数据库被破坏问题,可以通过如下命令进行数据库修复

-
1
2
3
4
cd /var/lib/rpm/
for i in `ls | grep 'db.'`;do mv $i $i.bak;done
rpm --rebuilddb
yum clean all
-

-

卸载

-

应用卸载

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 快速删除
yum remove mysql mysql-server mysql-libs mysql-server
# 查找残留文件(例如:mysql-community-server-8.0.22-1.el7.x86_64)
rpm -qa | grep -i mysql
# 将查询出来的应用删除
yum remove mysql-community-server-8.0.22-1.el7.x86_64
# 查找残余目录
## /etc/selinux/targeted/active/modules/100/mysql
## /etc/selinux/targeted/tmp/modules/100/mysql
## /var/lib/mysql
## /var/lib/mysql/mysql
## /usr/share/mysql
find / -name mysql
# 删除残余目录
rm -rf /etc/selinux/targeted/active/modules/100/mysql
rm -rf /etc/selinux/targeted/tmp/modules/100/mysql
rm -rf /var/lib/mysql
rm -rf /var/lib/mysql/mysql
rm -rf /usr/share/mysql
# 再次检查残余目录,确保没有残余
find / -name mysql
-

开机自启(可选)

-

当你安装时设置了开机启动,当不需要再开机启动时,那么再卸载完数据库后,记得在开启启动的列表中删除数据库

-
1
2
chkconfig --list | grep -i mysql
chkconfig --del mysqld
-

附录

-
    -
  • How To Install MySQL on CentOS 7
  • -
  • ERROR 1130
  • -
  • ERROR 2059
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/08/01/syncing-a-fork/index.html b/2018/08/01/syncing-a-fork/index.html deleted file mode 100644 index dc9b0863b..000000000 --- a/2018/08/01/syncing-a-fork/index.html +++ /dev/null @@ -1,529 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Git 同步 Fork 项目 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Git 同步 Fork 项目 -

- - -
- - - - -

Github 全球最大的同性交友网站,这里拥有最前沿的IT技术创新,拥有最流行的开源项目,等等…,总之这里是我的知识仓库,每天都会在上面寻找,学习知识

-

扯远了,本篇解决对于fork的项目,如何进行源项目的更新和同步问题

- -

远程仓库

-
    -
  1. 查看fork项目的远程仓库信息
    1
    2
    3
    git remote -v
    origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
    origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
    -
  2. -
  3. 设置源项目仓库地址
    1
    git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git
    -
  4. -
  5. 检查远程地址信息
    1
    2
    3
    4
    5
    git remote -v
    origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
    origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
    upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (fetch)
    upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (push)
    -
  6. -
-

同步源仓库信息

-
    -
  1. 获取源仓库更新
    1
    2
    3
    4
    5
    6
    7
    git fetch upstream
    remote: Counting objects: 75, done.
    remote: Compressing objects: 100% (53/53), done.
    remote: Total 62 (delta 27), reused 44 (delta 9)
    Unpacking objects: 100% (62/62), done.
    From https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY
    * [new branch] master -> upstream/master
    -
  2. -
  3. 查看本地master分支
    1
    2
    git checkout master
    Switched to branch 'master'
    -
  4. -
  5. 合并源仓库更新到本地master分支
    1
    2
    3
    4
    5
    6
    7
    8
    git merge upstream/master
    Updating a422352..5fdff0f
    Fast-forward
    README | 9 -------
    README.md | 7 ++++++
    2 files changed, 7 insertions(+), 9 deletions(-)
    delete mode 100644 README
    create mode 100644 README.md
    -
  6. -
-

同步源仓库branch

-

在git中master实质是一个特殊的branch,其它的branch的同步和master同步操作并不一样

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看项目的所有分支
git branch -v
# 当前项目在master分支,origin/HEAD类似指针,表示项目默认分支是origin/master
# origin/dev,origin/i18n,origin/ivan/feat-custom-lang,origin/master这四个分支是fork项目目前拥有分支
# upstream/dev,upstream/i18n,upstream/master表示源仓库项目所拥有的分支
* master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/i18n
remotes/origin/ivan/feat-custom-lang
remotes/origin/master
remotes/upstream/dev
remotes/upstream/i18n
remotes/upstream/master

# 切换到dev分支,同步源仓库dev分支到fork项目的dev分支
git checkout -b dev upstream/dev
# 推送修改到fork项目dev分支
git push origin dev
-
-

如果源仓库分支已被删除,那么可以在fork项目中删除源仓库已被删除的分支

-
-
1
2
# 删除指定分支,并推送到远程仓库
git push origin --delete branch_name
-

同步源仓库tag

-
1
2
3
4
# 获取源仓库的tag
git fetch upstream --tags
# 将新的的tag推送到fork项目
git push --tags
-

附录

-
    -
  • 同步你的 Fork 仓库
  • -
  • Configuring a remote for a fork
  • -
  • Syncing a fork
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/10/02/rxjava/index.html b/2018/10/02/rxjava/index.html deleted file mode 100644 index eef9209a7..000000000 --- a/2018/10/02/rxjava/index.html +++ /dev/null @@ -1,647 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -RxJava 入门 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- RxJava 入门 -

- - -
- - - - -

RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.(一个在 Java VM 上使用可观测的序列( 观察者模式 )来组成异步的、基于事件的程序的库).

-

在实际开发过程中,RxJava已是一个不可或缺的组件,因此对于RxJava的学习和思考,记录分享是很重要的一个环节

-

本系列文章主要:

-
    -
  1. RxJava 入门
  2. -
  3. RxJava 实际应用
  4. -
  5. RxJava 源码剖析
  6. -
- -

目前来说,RxJava有两个版本,RxJava1 与 RxJava2 两个版本之间虽然存在很多不同,但它们的本质是相同,由于对于RxJava1 已废弃,因此建议没有学习或者是使用过,可直接上手学习RxJava2(在学习过程中部分地方还是会有RxJava1相关的说明,但这不是重点)

-

文章使用RxJava版本如下:

-
    -
  • implementation 'io.reactivex:rxjava:1.3.0'
  • -
  • implementation 'io.reactivex.rxjava2:rxjava:2.2.1'
  • -
  • 项目示例:rc-cluster-network
  • -
-
-

由于一个项目中RxJava1与RxJava2并不能共存,因此实际参考项目中仅RxJava2示例

-
-

RxJava 基础

-

RxJava1 VS RxJava2

-

rxjava1 vs rxjava2

-
-

以上是列举出不同版本间主要的变换,其它更细节部分,请查看官方Wiki

-
-

关键词

-

RxJava1

-
    -
  • Observable (可观察者,即被观察者)
  • -
  • Observer (观察者)
  • -
  • subscribe (订阅)
  • -
  • 事件
  • -
-
-

Observable和Observer通过subscribe()方法实现订阅关系,从而Observable可以在需要的时候发出事件来通知Observer

-
-

RxJava2

-
    -
  • Observable (可观察者,即被观察者)
  • -
  • Observer (观察者)
  • -
  • ObservableEmitter (发射器)
  • -
  • 事件
  • -
-
-

RxJava2中SubscrberObservableEmitter取代,Observer中多了一个回调方法 onSubscribe(),传递参数为Disposable

-
-
    -
  • ObservableEmitter:Emitter是发射器的意思,这个就是用来发出事件,它可以发出三种类型的事件,通过调用emitteronNext(T value)onComplete()onError(Throwable e)就可以分别发出next事件,complete事件和error事件
  • -
  • Disposable:字面意思是一次性用品,用完即可丢弃。在RxJava中可以理解成两根管道间的阀门,当调用它的的dispose()方法时,它就将两根管道切断,从而导致下游收不到事件,即相当于Subsciption
  • -
-

基本实现

-

Create Observable

-

operators

-
RxJava1
-
1
2
3
4
5
6
7
8
9
Observable<String> observable = Observable.unsafeCreate(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("World");
subscriber.onNext("RxJava1");
subscriber.onCompleted();
}
});
-
-

1.2.7版本后,Observable的create()方法已被废弃,如果没有特殊需求,可以使用unsafeCreate()代替,构造Obaservable实例

-
-
RxJava2
-
1
2
3
4
5
6
7
8
9
Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext("Hello");
emitter.onNext("World");
emitter.onNext("RxJava2");
emitter.onComplete();
}
});
-

unsafeCreate()/create()方法是RxJava最基本创建时间序列的方法。基于这个方法,RxJava还提供了一些方法来快捷创建事件队列

-
    -
  • just(T…):将传入的参数依次发送出来
    1
    2
    3
    4
    5
    6
    Observable observable = Observable.just("Hello", "World", "RxJava");
    // 将会依次调用:
    // onNext("Hello");
    // onNext("World");
    // onNext("RxJava");
    // onCompleted();
    -
  • -
  • from(T[])/from(Iierabble<? extends T>):将传入的数组或Iterable拆分成具体对象后,依次发送出来
    1
    2
    3
    4
    5
    6
    7
    String[] words = {"Hello", "World", "RxJava"};
    Observable observable = Observable.from(words);
    // 将会依次调用:
    // onNext("Hello");
    // onNext("World");
    // onNext("RxJava");
    // onCompleted();
    -
  • -
-
Flowable
-

Flowable是RxJava2中新增的类,专门应对背压(Backpressure)问题,但这个概念并不是RxJava2中引入的概念。

-

出现Flowable的原因:即生产者(被观察者发送事件)的速度与消费者(观察者接收所有事件)的速度不匹配,从而导致观察者无法及时响应/处理所有发送过来的事件问题,最终导致缓冲区溢出,事件丢失 & OOM等问题。

-

一般情况,被观察者发送事件速度 > 观察者接收事件速度。比如:点击过快造成等

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Flowable.create(new FlowableOnSubscribe<String>() {

@Override
public void subscribe(FlowableEmitter<String> emitter) throws Exception {
emitter.onNext("Hello");
emitter.onNext("World");
emitter.onNext("RxJava2");
emitter.onComplete();
}
}, BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.newThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
// 相当于onNext
Thread.sleep(1000);
System.out.println("accept");
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
// 相当于onError
System.out.println("accept" + throwable.toString());
}
});
-

Flowable并不是订阅就开始发送数据,而是需等到执行Subscription.request()才开始发送数据

-

Create Observer

-
RxJava1
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Observer<String> observer = new Observer<String>() {
@Override
public void onCompleted() {
System.out.println("Completed!");
}

@Override
public void onError(Throwable e) {
System.out.println("Error" + e);
}

@Override
public void onNext(String s) {
System.out.println("Next" + s);
}
};
-

除了Observer接口之外,RxJava内置了一个实现Observer的抽象类SubscriberSubscriberObserver接口进行了一些扩展,但它们的基本使用方式是完全一样

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Subscriber<String> subscriber = new Subscriber<String>() {
@Override
public void onCompleted() {
System.out.println("Completed!");
}

@Override
public void onError(Throwable e) {
System.out.println("Error" + e);
}

@Override
public void onNext(String s) {
System.out.println("Next" + s);
}
};
-

实际,在RxJava的subscribe过程中,Observer也总是会先被转成一个Subscriber再使用。对于使用者来说ObserverSubscriber的主要区别是:

-
    -
  1. onStart():这是Subscriber增加的方法。它会再subscribe刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如:数据的重置等操作。这是一个可选方法,默认情况下它的实现为空。
  2. -
-
-

注意:
-对于准备工作有线程要求,onStart()就不适用,因为它总是再subscribe所发生的线程被调用,而不能指定线程。要指定线程来准备工作,可以使用doOnSubscribe()方法

-
-
    -
  1. unsubscribe():Subscriber所实现的另一个接口Subscription的方法,用于取消订阅。在这个方法被调用后,Subscriber将不再接收事件。
  2. -
-
-

注意:

-
    -
  • 一般需要在调用unsubscribe()方法前,需要使用isUnsubscribed()先判断状态。
  • -
  • 不再使用的时候尽快在合适的地方调用unsubscribe()来解除引用关系,以避免内存泄漏
  • -
-
-
RxJava2
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Observer<String> observer = new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
System.out.println("Subscribe: " + d);
}

@Override
public void onNext(String s) {
System.out.println("Next: " + s);
}

@Override
public void onError(Throwable e) {
System.out.println("Error: " + e);
}

@Override
public void onComplete() {
System.out.println("Complete !");
}
};
-

Subscribe

-

创建好ObservableObserver之后,再用subscribe()方法将它们联结起来

-
1
2
3
observable.subscribe(observer);
// 或者(仅支持RxJava1)
observable.subscribe(subscriber);
-

chain calls

-

以上三步是使用RxJava进行异步操作的基本过程,创建被观察者,创建观察者被观察者订阅观察者,我们可以通过链式调用形式完成操作

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Observable.unsafeCreate(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("World");
subscriber.onNext("RxJava1");
subscriber.onCompleted();
}
}).subscribe(new Observer<String>() {
@Override
public void onCompleted() {
System.out.println("Completed!");
}

@Override
public void onError(Throwable e) {
System.out.println("Error" + e);
}

@Override
public void onNext(String s) {
System.out.println("Next" + s);
}
});
-

简化订阅

-

除了subscribe(Observer)subscribe(Subscriber)(仅支持RxJava1)subscribe()还支持不完整的简化订阅回调

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// RxJava1
Action1<String> onNextAction = new Action1<String>() {
@Override
public void call(String s) {
System.out.println("onNext" + s);
}
};

Action1<Throwable> onErrorAction = new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
System.out.println("onError" + throwable);
}
};

Action0 onCompletedAction = new Action0() {
@Override
public void call() {
System.out.println("completed");
}
};

// RxJava2
Consumer<String> onNextAction = new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
System.out.println("onNext" + s);
}
};

Consumer<Throwable> onErrorAction = new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
System.out.println("onError" + throwable);
}
};

Action onCompleteAction = new Action() {
@Override
public void run() throws Exception {
System.out.println("complete");
}
};

// 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext()
observable.subscribe(onNextAction);
// 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和 onError()
observable.subscribe(onNextAction, onErrorAction);
// 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来定义 onNext()、 onError() 和 onCompleted()
observable.subscribe(onNextAction, onErrorAction, onCompletedAction/onCompleteAction);

-

RxJava 线程

-

在RxJava的默认规则中,事件的发出和消费都是在同一个线程(在哪个线程条用subscriber(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件),也就是说,以上RxJava基本操作,实现出来的只是一个同步的观察者模式。而观察者模式本身的目的是“后台处理,前台回调”的异步机制,因此在RxJava中通过Scheduler来对线程进行管理

-

Scheduler API

-

Scheduler相当于线程控制器,RxJava通过它指定代码应该运行在什么样的线程,其中RxJava中内置了几个Scheduler

-
    -
  • Schedulers.computation():计算所使用的Scheduler。这个计算值的是CPU密集型计算,即不会被I/O操作等限制性能的操作。不要把I/O操作放在computation()中,否则I/O操作的等待时间会浪费CPU。
  • -
  • Schedulers.form(Executor):
  • -
  • Schedulers.immediate():直接在当前线程运行,相当于不指定线程,这也是默认的Scheduler.
  • -
  • Schedulers.io():I/O操作(读写文件,读写数据库,网络信息交换等)所使用的Scheduler。行为模式和newThread()差不多,区别在于io()的内部实现是一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下io()比newThread()更高效
  • -
  • Schedulers.newThread():总是启用新线程,并在新线程执行操作
  • -
  • Schedulers.single()『仅RxJava2中存在』:
  • -
  • Schedulers.test()『仅RxJava1中存在』:顾名思义,这是一个测试
  • -
  • Schedulers.trampoline():
  • -
  • AndoroidSchedulers.mainThread():指定操作在Android的主线程
  • -
-

有了Scheduler,我们可以使用subscribeOn()observeOn()方法来对线程进行控制

-
    -
  • -

    subscribeOn():指定subscribe()所发生的线程,即Observable.OnSubscribe被激活时所处的线程,或者叫做事件的产生的线程

    -
  • -
  • -

    observeOn():指定Subscriber所运行的线程。或者叫做事件的消费线程

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // RxJava2
    Observable.create(new ObservableOnSubscribe<String>() {
    @Override
    public void subscribe(ObservableEmitter<String> emitter) throws Exception {
    emitter.onNext("Hello");
    emitter.onNext("World");
    emitter.onNext("RxJava2");
    emitter.onComplete();
    }
    })
    // 指定 subscribe() 发生在 IO 线程
    .subscribeOn(Schedulers.io())
    // 指定 Subscriber 的回调发生在主线程
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Consumer<String>() {
    @Override
    public void accept(String s) throws Exception {
    System.out.println(s);
    }
    });
    -
  • -
-

操作符

-

说RxJava好用,还有一个原因是RxJava提供了大量的操作符,这些操作符保证了在面都复杂的逻辑下,依旧可以是逻辑清晰的链式调用

-

RxJava_action

-

总结

-

本篇文章作为RxJava系列的学习的入门,不会讲解相关操作的原理等
-学习目的

-
    -
  • 了解RxJava1与RxJava2之间的不同点,
  • -
  • 了解RxJava的线程管理,
  • -
  • 掌握完成RxJava的基本操作,
  • -
  • 清楚RxJava操作符,以及分别适用于什么样的场景
  • -
-

附录

-

文章中部分原话引用了参考学习文章的原话,在这里向那些无私分享的大佬致敬

-
    -
  • 给 Android 开发者的 RxJava 详解
  • -
  • RxJava系列教程
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/10/03/movie-fierce/index.html b/2018/10/03/movie-fierce/index.html deleted file mode 100644 index 0356e0471..000000000 --- a/2018/10/03/movie-fierce/index.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -《激战》 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 《激战》 -

- - -
- - - - -
-

怕,你就会输一辈子

- -
-

喜欢其中的一些台词,大伙共勉

-
    -
  • 其实,我每次上台都很怕的,不过每次我都会跟自己说,我能做到
  • -
  • 这场比赛我可能会跌倒,但我一定会站起来
  • -
  • 怕,你就会输一辈子
  • -
- -

自己的一些感触:
-其实很多时候,道理都懂,但却不能坚持下去,但这些道理都在自己生活中一点点的用生活感悟出来,那这些道理会更浓烈,更让人刻骨铭心

-
    -
  • 尊重和珍惜,那些愿意为你去花时间的人
  • -
  • 要和自己志同道合,有共同目标的伙伴去互相较劲
  • -
  • 从哪里跌倒就要从哪里爬起来
  • -
  • 一路跌跌撞撞走下去,中间的酸甜苦辣是最美的味道
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/10/06/git-account/index.html b/2018/10/06/git-account/index.html deleted file mode 100644 index fea1d6c78..000000000 --- a/2018/10/06/git-account/index.html +++ /dev/null @@ -1,539 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Git 多账号 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Git 多账号 -

- - -
- - - - -

以前,git的账号只用来在Github上操作,随着积累Git管理的项目不仅仅只来自Github,还有一些其它Git项目托管的平台,例如:BitbucketCodingGiteeGitlib,以及公司内Git仓库

-

不同的托管平台有着不同的Git账号,无法用一个账号来管理其它的仓库,而且由于不同的托管平台账号不同,因此需要添加不同账号的公钥,这样我们再能在对应平台用对应的账号进行操作

- -

环境

-
    -
  • Windows 10 x64
  • -
  • Git version 2.16.0
  • -
-
-

这里Git的安装不在赘述

-
-

生成对应账号的密钥

-
1
2
3
4
5
6
# 进入到`your_pc_name/.ssh`,
cd .ssh
# Jerry.x@outlook.com 是我的Github的邮箱,这里需要替换成自己的邮箱
ssh-keygen -t rsa -C "Jerry.x@outlook.com"
# 命名文件名称或指定文件存放路径等
# 其它可以回车键进行确认,进行下一步
-

git-account

-
-

完成后,将会生成 id_rsa_company.pub(存放公钥)与 id_rsa_company(存放私钥)两个文件

-
-

添加公钥到托管平台

-
    -
  • .ssh 路径下,用文本编辑器打开 id_rsa_company.pub 文件,复制内容
  • -
  • 在托管平台上添加 ssh public key
  • -
-

以下以 GitHub 添加为例,其它平台类似
-git-add-key

-

添加配置文件

-

.ssh 路径下,创建 config 文件,无文件后缀名,如下示例

-
1
2
3
4
5
6
7
8
9
10
11
12
13
# 配置github.com
Host github.com
HostName github.com
IdentityFile C:\\Users\\Jerry\\.ssh\\id_rsa
PreferredAuthentications publickey
User BladeCode

# 配置 company.domain.com
Host company.domain.com
HostName company.domain.com
IdentityFile C:\\Users\\Jerry\\.ssh\\id_rsa_company
PreferredAuthentications publickey
User Jerry xu
-
    -
  • Host:的名字可以取为自己喜欢的名字
  • -
  • HostName:这个是真实的域名地址
    -例如:https://github.com/BladeCode/BladeCode.github.io.git,红色标注字段
  • -
  • IdentityFile:这里是id_rsa的地址
  • -
  • PreferredAuthentications:配置登录时用什么权限认证
    -可设为publickey,password publickey,keyboard-interactive 等
  • -
  • User:配置使用用户名
  • -
-

测试

-
1
ssh -T git@github.com
-

git-test

-
-

git@github.comgithub.com 就是上一步中 config 文件中配置的 HostName 字段内容

-
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/10/07/git-bash/index.html b/2018/10/07/git-bash/index.html deleted file mode 100644 index 3895726c4..000000000 --- a/2018/10/07/git-bash/index.html +++ /dev/null @@ -1,583 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Git 常用命令 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Git 常用命令 -

- - -
- - - - -

- -

记录 Git 日常操作常用命令

-

git config

-

Git级别:system(系统所有用户) < global(当前用户) < local(当前仓库)

-
    -
  • -

    查看配置信息

    -
    1
    2
    # 查看对应 Git 级别(--local;--global;--system)的配置信息
    git config --list --local
    -
  • -
  • -

    新增或修改

    -
    1
    2
    git config --global user.name xxxxx
    git config --global user.email xxx@xxxx.com
    -
  • -
  • -

    删除用户配置信息

    -
    1
    2
    # 如果当前只有一个用户,就不用加入xxxx
    git config --global --unset user.name xxxx
    -
  • -
-

git init

-
    -
  1. 把已有项目代码纳入 Git 管理
    1
    2
    3
    4
    # 进入项目根路径
    cd project_dir
    # 进行项目 Git 初始化
    git init
    -
  2. -
  3. 新建项目直接使用 Git 管理
    1
    2
    3
    4
    # 在当前路径下创建项目并使用 Git 初始化项目
    git init project_name
    # 进入项目根路径
    cd project_name
    -
  4. -
-

git clone

-
    -
  • clone
    1
    git clone url
    -
  • -
  • clone 指定分支
    1
    git clone -b branch_name url
    -
  • -
  • clone 指定tag
    1
    2
    3
    4
    # clone 
    git clone url
    # checkout tag
    git checkout tag_name
    -
  • -
  • clone 指定commit
    1
    2
    3
    4
    # 查看git commit 历史的
    git log
    # 指定 commit SHA
    git clone commit_sha_value
    -
  • -
-

git commit

-
1
git commit -m "注释"
-
-

-a 指定标签名,-m 指定说明文字

-
-

git branch

-
    -
  • 创建分支
    1
    2
    3
    4
    # 创建分支
    git branch branch_name
    # 创建并切换到新分支
    git checkout -b branch_name
    -
  • -
  • 切换分支
    1
    git checkout branch_name
    -
  • -
  • 删除分支
    1
    2
    3
    4
    # 删除本地分支
    git branch -d branch_name
    # 删除远程指定分支
    git push origin --delete branch_name
    -
  • -
  • 重命名分支
    1
    git branch -m old_branch_name new_branch_name
    -
  • -
  • 查看分支
    1
    2
    3
    4
    5
    6
    # 查看本地所有分支
    git branch
    # 查看远程所有分支
    git branch -r
    # 查看本地和远程所有分支
    git branch -a
    -
  • -
-

git tag

-
    -
  • 新增 tag
    1
    git tag -a tag_name -m "注释"
    -
  • -
  • 查看 tag
    1
    2
    3
    4
    # 查看指定 tag 信息
    git show tagname
    # 查看所有 tag
    git tag -l
    -
  • -
  • 删除 tag
    1
    2
    3
    4
    # 删除本地tag
    git tag -d tag_name
    # 删除远程指定tag
    git push origin --delete tag tag_name
    -
  • -
  • 推送 tag 到远程
    1
    2
    3
    4
    # push 单个 tag
    git push origin tag_name
    # push 所有 tag
    git push [origin] --tags
    -
  • -
-

git mv

-
    -
  • 重命名文件
    1
    git mv old_file_name new_file_name
    -
  • -
-

git log

-
    -
  • 查看仓库 commit 历史日志
    1
    2
    # 下面参数可任意组合
    git log --oneline(简洁查看) --all(所有分支) -n4(最近 4 次记录) --graph(图形化展示)
    -
  • -
-

git help

-

更多命令
-

1
git --help

-

git other

-
    -
  • 查看当前项目远程仓库地址
    1
    git remote -v
    -
  • -
  • 修改仓库地址
    1
    2
    3
    4
    5
    6
    # 方式一:直接修改
    git remote set-url origin [url]
    # 方式二:先删后加
    git remote rm origin
    git remote add origin [url]
    # 方式三:直接修改config文件
    -
  • -
-

附录

-
    -
  • Git Docs
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/10/25/fiddler/index.html b/2018/10/25/fiddler/index.html deleted file mode 100644 index ecf8b72cc..000000000 --- a/2018/10/25/fiddler/index.html +++ /dev/null @@ -1,544 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Fiddler 初体验 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Fiddler 初体验 -

- - -
- - - - -

在开发的路上,有时候面对一些应用,我们可能回去分析研究它的实现以及数据交互等,在没有官方没有公开的Api提供时,我们会用到一项实用的技术,抓包,所谓的抓包,指的是截取网络传输发送与接收的数据包。其中在Windows平台上使用比较广泛的要数Fiddler

-

本节主要讲解Fiddler的相关配置及简单使用

- -

资源

-
    -
  • Windows 10
  • -
  • Fiddler
  • -
-

配置

-

需要使手机连接WiFi和电脑WiFi是使用同一个网络

-

Fiddler 初始化

-

fiddler-config

-
-

默认端口:8888

-
-

网络地址

-

获取电脑所连接的网络IP地址
-fiddler-ip
-这里获取的IP地址,将用于手机连接网络的代理

-

手机配置

-

关于手机相关的配置操作,步骤已经通过下面的视频展现。

-
    -
  1. 连接与电脑相同的WiFi
  2. -
  3. 修改网络代理
  4. -
  5. 手动模式,并设置电脑端获取的IP地址及Fiddler默认端口号8888
  6. -
  7. 网络连接刷新
  8. -
  9. 获取并下载安装Fiddler证书
  10. -
-

其它

-

通过以上操作,现在可以在电脑端Fiddler工具中,拦截获取经过的所有网络信息。而我们一般是查看或者是分析某一款应用的数据信息,这样在查看起来就比较费力,那么我们就借助Fiddler提供的过滤功能
-fiddler-filter

-

选择过滤方式中

-
    -
  1. 第一项有三个选项,不做更改:
    -“No zone filter”;
    -“Show Only Intranet Hosts”;
    -“Show Only Internet Hosts”
  2. -
  3. 第二个选项是只监控以下网址,如只监控百度,在下面的输入框里填上 www.baidu.com
    -“No Host Filter”:不设置hosts过滤
    -“Hide The Following Hosts”:隐藏过滤到的域名
    -“Show Only The Following Hosts”:只显示过滤到的域名
    -“Flag The Following Hosts”:标记过滤到的域名
  4. -
  5. 文本框内输入需要过滤的域名,多个域名使用”;“分号分割。
  6. -
-
-

fiddler默认会检查http头中设置的host,强制显示http地址中域名。

-
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/10/26/android-audio-base/index.html b/2018/10/26/android-audio-base/index.html deleted file mode 100644 index abbe5e887..000000000 --- a/2018/10/26/android-audio-base/index.html +++ /dev/null @@ -1,629 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Android 音频基础知识 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Android 音频基础知识 -

- - -
- - - - -

关于音频技术是一门庞大且很专业的学术,这里不会阐述该知识的底层原理知识(比如:声音的原理,音波的正弦平面波合成等等),主要介绍音频相关的一些基本的知识概念,以及在实际开发过程中需要掌握关键API等。

-

声音

-

“声音是振动产生的声波,通过介质气体固体液体)传播并能被人或动物听觉器官所感知的波动现象”。声音的频率一般以赫兹表示,记为Hz,指每秒周期性震动的次数

- -

trasound_range_diagram

-
-

图片来自Wikipedia

-
-
    -
  • 红:次声波(由火山爆发、龙卷风、雷暴、台风等许多灾害性事件发生前都会产生出次声波,人们就可以利用这种前兆来预报灾害事件的发生)
  • -
  • 蓝:可听声波(20~20000Hz)
  • -
  • 绿:超声波(广泛应用于工业、军事、医疗等行业。在工业上,常用超声波来清洗精密零件,原理是利用超声波在清洗液中产生震荡波,使清洗液产生瞬间的小气泡,从而冲洗零件的每个角落)
  • -
-

音频开发应用场景

-
    -
  • 音频播放器,录音机
  • -
  • 语音电话
  • -
  • 音视频监控
  • -
  • 音视频直播
  • -
  • 音视频编辑/处理软件
  • -
  • 蓝牙耳机/音响等
  • -
-

音频开发具体内容

-
    -
  • 音频采集/播放
  • -
  • 音频算法处理(去噪,静音检测,回声消除,音效处理,功放/增强,混音/分离,等等)
  • -
  • 音频的编解码和格式转换
  • -
  • 音频传输协议的开发(SIPA2DPAVRCP,等等) -
      -
    • SIP(Session Initiation Protocol:会话发起协议):一个由IETF MMUSIC工作组开发的协议,作为标准被提议用于建立,修改和终止包括视频,语音,即时通信,在线游戏和虚拟现实等多种多媒体元素在内的交互式用户会话
    • -
    • A2DP(Advance Audio Distribution Profile:蓝牙立体声音频传输规范):规定了使用蓝牙异步传输信道方式,传输高质量音乐文件数据的协议堆栈软件和使用方法,基于该协议就能通过以蓝牙方式传输高质量的立体声音乐
    • -
    • AVRCP(Audio Video Remote Control Profile:音频/视频远程控制配置文件):用于提供控制 TV、Hi-Fi 设备等的标准接口。此配置文件用于许可单个远程控制设备。
    • -
    -
  • -
-

音频基础知识

-

声音经过麦克风采集后,得到是模拟信号,接着我们需要用程序将采集得到模拟型号,进行转换得到数字信号,这样我们才可以存储,交换等

-
-

关于声音信息得到模拟信号的转换,我们一般是无需关心,设备的麦克风这些都已经帮我们转换好了,我们需要关心的是从麦克风得到的模拟信号,如何去转换为数字信号,最终保存为音频文件

-
-

模拟信号转数字信号

-

模拟信号一般通过PCM(Pulse-code modulation:脉冲编码调制)方法转换为数字信号

-

转换步骤

-
    -
  1. 采样:将一段时间内的连续信号转为离散信号 -
      -
    • 模拟信号本身是一种连续信号,它在一定的时间范围内可以有无限多个不同的取值
    • -
    • 数值信号指在取值上是离散的,不连续的信号
    • -
    -
  2. -
  3. 量化:值采样得到后的数据,我们用多少位的二进制数字来表示声音的振幅
  4. -
  5. 编码:将采样量化后的数据按照一定的格式进行记录
  6. -
-

PCM

-

音频编码最多只能做到无限接近,至少目前的技术只能这样,相对自然界的信号,任何数字音频编码方式都是有损,因为无法完全还原。在计算机应用中,能够达到最高保真的就是PCM编码,因此PCM约定俗成了无损编码(PCM代表了数字音频中最佳的保真水平,并不意味着PCM就能够确保信号绝对保真,PCM也只能做到最大程度的无限接近)

-

经过采集和量化后的声音信号已经是数字形式了,但是为了便于计算机的存储,处理,传输,还必须按照一定的要求进行数据压缩编码

-
压缩
-

一种音频文件格式可以支持多种编码,例如AVI文件格式,但多数的音频文件仅支持一种音频编码

-

主要的音频文件格式:

-
    -
  • 无损格式,例如:WAVFLACAPEALACWavPack(WV)
  • -
  • 有损格式,例如:MP3AACOgg VorbisOpus
  • -
-
编码
-

根据编码方式的不同,音频编码技术分为三种

-
    -
  • 波形编码:音质质量高,编码速率也很高。脉冲编码调变(PCM)、自适应增量调制( ADM )、Adaptive( ADPCM )等都属于该类编码器。
  • -
  • 参数编码:音质质量低,编码速率也很低
  • -
  • 混合编码:音质和速率介于波形编码,参数编码之间
  • -
-
-

为什么音频需要编码

-
-
    -
  1. PCM所量化得到的数据是原始无损的数据,文件很大,不利于传播,存储等
  2. -
  3. 如果都是未压缩的文件,那么基本无法做到差异化即部分需要知识产权保护的组织或机构等
  4. -
-

音频开发中重要参数

-

采样率(samplerate)

-

指每秒从连续信号中提取并组成离散信号的采样个数,也就是1S内,对模拟信号进行多少次采样;采样频率越高,说明采样点之间越密集,记录这段音频所用的数据量就越大,因此音质也就越好

-
-

为什么通用的采样率是44.1kHz?

-
-

量化精度(位宽)

-

用二进位来表示每一个采样值,也称为量化位数,声音信号的量化位数一般是4,6,8,12或16 bits.

-

这个数值的数据类型大小可以是:4bit,8bit,16bit,32bit等等,位数越多,表示的就越精细,声音的质量也就越好,当然文件大小也会成倍增大

-

声道数(channels)

-

由于音频的采集和播放是可以叠加的,因此,可以同时从多个音频源采集声音,并分别输出到不同的扬声器,故声道数一般表示声音录制时的音源数量或回放时相应的扬声器数量

-
    -
  • 单声道(Mono):1
  • -
  • 双声道(Stereo):2
  • -
-

比特率

-

比特率是音频文件每秒占据的字节数(比特数)

-

比特率规定适用“比特每秒”(bit/sbps)为单位,其中ps指的是/s,即每秒。

-

通常我们在音乐播放软件中看到的音乐质量『标准(128kbit/s),较高(198kbit/s),极高(320kbit/s)』表述的即比特率

-

音频帧(frame)

-

视频每一帧就是一张图像,而音频数据是流式,本身没有明确的一帧帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗称2.5ms~60ms为单位的数据量为一帧音频。

-

理论音频的大小

-

假设某通道的音频信号是采样率为8kHz,位宽为16bit,20ms一帧,双通道,则一帧音频数据的大小为:

-
1
2
# 一帧音频的大小
int size = 8000 x 16bit x 0.02s x 2 = 5120 bit = 640 byte;
-

音频处理开源项目

-

VoIP相关

-

基于IP的语音传输(英语:Voice over Internet Protocol,缩写为VoIP)是一种语音通话技术,经由网际协议(IP)来达成语音通话与多媒体会议,也就是经由互联网来进行通信。其他非正式的名称有IP电话(IP telephony)、互联网电话(Internet telephony)、宽带电话(broadband telephony)以及宽带电话服务(broadband phone service)。

-
    -
  • imsdroid
  • -
  • sipdroid
  • -
  • csipsimple
  • -
  • linphone
  • -
  • WebRTC
  • -
-

算法相关

-
    -
  • ffmpeg
  • -
  • speex
  • -
-

其他

-

MP3编码库

-
    -
  • Lame
  • -
-

Android提供相关API

-
    -
  • 音频采集:MediaRecoder,AudioRecord
  • -
  • 音频播放:SoundPool,MediaPlayer,AudioTrack
  • -
  • 音频编解码:MediaCodec
  • -
  • NDK API:OpenSL ES
  • -
-

附录

-
    -
  • 语音编码
  • -
  • 高级音频编码 ● AAC
  • -
  • 音频技术可以延展众多应用场景
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/10/27/android-audio/index.html b/2018/10/27/android-audio/index.html deleted file mode 100644 index 3f51ac406..000000000 --- a/2018/10/27/android-audio/index.html +++ /dev/null @@ -1,736 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Android 音频录制与播放 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Android 音频录制与播放 -

- - -
- - - - -

上一篇主要介绍了音频相关的一些基础知识,本篇主要介绍在Android系统中如何进行音频的录制,播放

-

音频录制

-

Android SDK中提供了AudioRecordMediaRecorder两个API经行音频的录制,具体的优缺点等如下:

-
    -
  • AudioRecord 『added in API level 3』(基于字节流录音):
    -优点:可以实现语音的实时处理,进行边录边播,对音频的实时处理。
    -缺点:输出的是PCM的语音数据,如果保存成音频文件是不能被播放器播放的。要用到这个去进行处理。
    -适用场景:需要实时处理分析的录音场景等,如:会说话的汤姆猫『AppStore | GooglePlay
  • -
- -
    -
  • MediaRecorder 『added in API level 1』(基于文件音视频录制):
    -优点:封装度很高,操作简单,无需处理中间录制过程;录制的音频文件是经过压缩的,需要设置编码器;录制的音频文件可以使用系统自带的播放器播放
    -缺点:无法实现实时处理音频,输出的音频格式少。
    -适用场景:录制过程需要实时处理的场景等
  • -
-

音频播放

-
    -
  • -

    AudioTrack『added in API level 3』:
    -AudioTrack 则更接近底层,提供了非常强大的控制能力,支持低延迟播放,适合流媒体和VoIP语音电话等场景

    -
  • -
  • -

    SoundPool 『added in API level 1』:
    -优点:主要用于播放一些较短的声音片段,支持从程序的资源或文件系统加载;CPU的资源占用量低、反应延迟小,并且可以加载多个音频到SoundPool中,通过资源ID来管理
    -缺点:SoundPool加载资源,最大只能申请 1MB 的内存控件,因此只能用来播放一些很短的声音片段
    -适用场景:播放短,反应要求高的音频

    -
  • -
  • -

    MediaPlayer 『added in API level 1』(基于字节流音视频播放):
    -优点:支持本地,网络音频资源的播放
    -缺点:资源占用量较高、加载延迟时间较长;不支持多个音频同时播放等
    -适用场景:播放长音频

    -
  • -
-
-

Google官方给出了兼容支持

-
-

AudioRecord

-

录制流程

-
    -
  1. 构造一个AudioRecord对象,其中需要的最小音频缓存buffer大小可以通过getMinBufferSize()方法得到,如果buffer容量过小,将导致对象构造失败
  2. -
  3. 初始化一个buffer,该buffer 大于等于AudioRecord对象用于写音频数据的buffer大小
  4. -
  5. 开始录音
  6. -
  7. 创建一个数据流,一边从AudioRecord中读取音频数据到初始化的buffer,一边将buffer中的数据导入数据流
  8. -
  9. 关闭数据流
  10. -
  11. 停止录音
  12. -
-

参数配置

-
    -
  • audioSource :音频采集的输入源 -
      -
    • DEFAULT(默认)
    • -
    • VOICE_RECOGNITION(用于语音识别,等同于DEFAULT)
    • -
    • MIC(由手机麦克风输入)
    • -
    • VOICE_COMMUNICATION(用于VoIP应用)
    • -
    -
  • -
  • sampleRateInHz:采样率
    -目前44100Hz是唯一可以保证兼容所有Android手机的采样率
  • -
  • channelConfig:通道数的配置 -
      -
    • CHANNEL_IN_MONO:单通道
    • -
    • CHANNEL_IN_STEREO:双通道
    • -
    -
  • -
  • audioFormat:数据位宽 -
      -
    • ENCODING_PCM_8BIT:8bit
    • -
    • ENCODING_PCM_16BIT:16bit
    • -
    -
  • -
  • bufferSizeInBytes:AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小
  • -
-

示例代码

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
public class AudioCapturer {

private static final String TAG = "AudioCapturer";

private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC;
private static final int DEFAULT_SAMPLE_RATE = 44100;
private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;

private AudioRecord mAudioRecord;
private int mMinBufferSize = 0;

private Thread mCaptureThread;
private boolean mIsCaptureStarted = false;
private volatile boolean mIsLoopExit = false;

private OnAudioFrameCapturedListener mAudioFrameCapturedListener;

public interface OnAudioFrameCapturedListener {
public void onAudioFrameCaptured(byte[] audioData);
}

public boolean isCaptureStarted() {
return mIsCaptureStarted;
}

public void setOnAudioFrameCapturedListener(OnAudioFrameCapturedListener listener) {
mAudioFrameCapturedListener = listener;
}

public boolean startCapture() {
return startCapture(DEFAULT_SOURCE, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG,
DEFAULT_AUDIO_FORMAT);
}

public boolean startCapture(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {

if (mIsCaptureStarted) {
Log.e(TAG, "Capture already started !");
return false;
}

mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
Log.e(TAG, "Invalid parameter !");
return false;
}
Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");

mAudioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize);
if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
Log.e(TAG, "AudioRecord initialize fail !");
return false;
}

mAudioRecord.startRecording();

mIsLoopExit = false;
mCaptureThread = new Thread(new AudioCaptureRunnable());
mCaptureThread.start();

mIsCaptureStarted = true;

Log.d(TAG, "Start audio capture success !");

return true;
}

public void stopCapture() {

if (!mIsCaptureStarted) {
return;
}

mIsLoopExit = true;
try {
mCaptureThread.interrupt();
mCaptureThread.join(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}

if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
mAudioRecord.stop();
}

mAudioRecord.release();

mIsCaptureStarted = false;
mAudioFrameCapturedListener = null;

Log.d(TAG, "Stop audio capture success !");
}

private class AudioCaptureRunnable implements Runnable {

@Override
public void run() {

while (!mIsLoopExit) {

byte[] buffer = new byte[mMinBufferSize];

int ret = mAudioRecord.read(buffer, 0, mMinBufferSize);
if (ret == AudioRecord.ERROR_INVALID_OPERATION) {
Log.e(TAG , "Error ERROR_INVALID_OPERATION");
}
else if (ret == AudioRecord.ERROR_BAD_VALUE) {
Log.e(TAG , "Error ERROR_BAD_VALUE");
}
else {
if (mAudioFrameCapturedListener != null) {
mAudioFrameCapturedListener.onAudioFrameCaptured(buffer);
}
Log.d(TAG , "OK, Captured "+ret+" bytes !");
}
}
}
}
}
-

AudioTrack

-

播放流程

-
    -
  1. 配置参数,初始化内部的音频播放缓冲区到,如果buffer容量过小,将导致对象构造失败
  2. -
  3. 开始播放
  4. -
  5. 需要一个线程,不断地向 AudioTrack 的缓冲区写入音频数据,注意,这个过程一定要及时,否则就会出现underrun的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“送入”音频数据,导致内部的音频播放缓冲区为空
  6. -
  7. 停止播放,释放资源
  8. -
-

参数配置

-
    -
  • streamType:当前应用使用的哪一种音频管理策略
    -当系统有多个进程需要播放音频时,这个管理策略会决定最终的展现效果 -
      -
    • STREAM_VOCIE_CALL:电话声音
    • -
    • STREAM_SYSTEM:系统声音
    • -
    • STREAM_RING:铃声
    • -
    • STREAM_MUSCI:音乐声
    • -
    • STREAM_ALARM:警告声
    • -
    • STREAM_NOTIFICATION:通知声
    • -
    -
  • -
  • sampleRateInHz:采样率
    -采样率的取值范围必须在 4000Hz~192000Hz 之间
  • -
  • channelConfig:通道数的配置 -
      -
    • CHANNEL_IN_MONO:单通道
    • -
    • CHANNEL_IN_STEREO:双通道
    • -
    -
  • -
  • audioFormat:数据位宽 -
      -
    • ENCODING_PCM_8BIT:8bit
    • -
    • ENCODING_PCM_16BIT:16bit
    • -
    -
  • -
  • bufferSizeInBytes:配置的是 AudioTrack 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小
  • -
  • mode:AudioTrack 播放模式 -
      -
    • MODE_STATIC
      -static:一次性将所有的数据都写入播放缓冲区,简单高效,通常用于播放铃声、系统提醒的音频片段
    • -
    • MODE_STREAM
      -streaming:按照一定的时间间隔不间断地写入音频数据,理论上它可用于任何音频播放的场景
    • -
    -
  • -
-

示例代码

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class AudioPlayer {

private static final String TAG = "AudioPlayer";

private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC;
private static final int DEFAULT_SAMPLE_RATE = 44100;
private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private static final int DEFAULT_PLAY_MODE = AudioTrack.MODE_STREAM;

private boolean mIsPlayStarted = false;
private int mMinBufferSize = 0;
private AudioTrack mAudioTrack;

public boolean startPlayer() {
return startPlayer(DEFAULT_STREAM_TYPE,DEFAULT_SAMPLE_RATE,DEFAULT_CHANNEL_CONFIG,DEFAULT_AUDIO_FORMAT);
}

public boolean startPlayer(int streamType, int sampleRateInHz, int channelConfig, int audioFormat) {

if (mIsPlayStarted) {
Log.e(TAG, "Player already started !");
return false;
}

mMinBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
if (mMinBufferSize == AudioTrack.ERROR_BAD_VALUE) {
Log.e(TAG, "Invalid parameter !");
return false;
}
Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");

mAudioTrack = new AudioTrack(streamType,sampleRateInHz,
channelConfig,audioFormat,mMinBufferSize,DEFAULT_PLAY_MODE);

if (mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED) {
Log.e(TAG, "AudioTrack initialize fail !");
return false;
}

mIsPlayStarted = true;

Log.d(TAG, "Start audio player success !");

return true;
}

public int getMinBufferSize() {
return mMinBufferSize;
}

public void stopPlayer() {

if (!mIsPlayStarted) {
return;
}

if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
mAudioTrack.stop();
}

mAudioTrack.release();
mIsPlayStarted = false;

Log.d(TAG, "Stop audio player success !");
}

public boolean play(byte[] audioData, int offsetInBytes, int sizeInBytes) {

if (!mIsPlayStarted) {
Log.e(TAG, "Player not started !");
return false;
}

if (sizeInBytes < mMinBufferSize) {
Log.e(TAG, "audio data is not enough !");
return false;
}

if (mAudioTrack.write(audioData,offsetInBytes,sizeInBytes) != sizeInBytes) {
Log.e(TAG, "Could not write all the samples to the audio device !");
}

mAudioTrack.play();

Log.d(TAG , "OK, Played "+sizeInBytes+" bytes !");

return true;
}
}
-

MediaRecorder

-

mediarecorder

-

如上所示表述整个MediaRecorder的整个生命过程,可以看出初始化之后,在任意的状态下调用reset()方法均可以回到MediaRecorder刚刚初始化完成的状态

-

MediaPlayer

-

mediaplayer

-

MediaPlayer 工作流程

-
    -
  1. 创建一个MediaPlayer对象
  2. -
  3. 调用setDataSource()方法,设置音频文件的路径
  4. -
  5. 接着调用prepare()方法,使MediaPlayer进入的准备状态
  6. -
  7. 调用start()方法,开始播放音频『pause()方法表示:暂停播放』
  8. -
-

MediaPlayer常用的控制方法

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
方法名功能描述
setDataSource()设置要播放的音频文件的位置
prepare()在开始播放之前调用这个方法完成准备工作
start()开始或继续播放音频
pause()暂停播放音频
reset()将MediaPlayer对象重置到刚刚创建的状态
seekTo()从指定位置开始播放音频
stop()停止播放音频。调用这个方法后的MediaPlayer对象无法再播放音频
release()释放掉与MediaPlayer对象相关的资源
isPlaying()判断当前MediaPlayer是否正在播放音频
getDuration()获取站如的音频文件的时长
-

注意事项

-
    -
  1. 在使用star()播放流媒体之前,需要装载流媒体资源。这里最好使用prepareAsync()异步的方式装载流媒体资源,在使用prepareAsync()异步加载时,为避免还没有装载完就调用了start()而保存,需要绑定MediaPlayer.setOnPreparedListener()事件,它将在异步装在完成后回调
    -原因:流媒体资源的装载是会消耗系统资源,在一些硬件不理想的设备上,如果使用prepare()同步的方式装载资源,可能会造成UI界面卡顿,其次避免装载超时而引发ANR等问题
  2. -
  3. 使用完MediaPlayer需要回收资源。MediaPlayer时很消耗系统资源的,所以在使用完MediaPlayer,及时主动回收资源
  4. -
  5. 对于单曲循环之类的操作,除了使用setLooping()方法设置之外,还可以为MediaPlayer注册回调函数,MediaPlayer.setOnCompletionListener(),它会在MediaPlayer播放完被回调
  6. -
  7. 由于无法确保播放的流媒体是完整(中间有错误),我们需要处理这个错误,否则会影响用户体验。可以在MediaPlayer中注册setOnErrorListener()错误回调,一般重新播放或者播放下一个流媒体
  8. -
-

跨平台

-

关于音频编解码在各平台上的情况如下
-wiki-ecode

-

从上图可知,AACFLACMP3三种编码是全平台支持的音频编码方式(或音频压缩方式),注意编码方式并不是文件格式即文件的扩展名

-
    -
  • AAC 主要扩展名 -
      -
    • .aac
    • -
    • .mp4
    • -
    • .m4a
    • -
    -
  • -
  • FLAC 扩展名 -
      -
    • .flac
    • -
    -
  • -
  • MP3 扩展名 -
      -
    • .mp3
    • -
    -
  • -
-

总结

-
    -
  • 音频的录制,Android SDK提供了两套音频采集的API,分别是:MediaRecorderAudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如:AMR,OGG等)并存储成文件,而后者则更接近底层,能够更加自由灵活的控制,可以得到原始的一帧帧PCM音频数据
  • -
  • 如果要简单的进行音频的采集,录制成音频文件,则推荐适用MediaRecorder,而如果需要对音频做进一步的算法处理,或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议适用AudioRecord
  • -
  • MediaRecorder底层的实现也是调用了AudioRecordAndroid Framework 层的AudioFlinger进行交互
  • -
-
-

关于音视频相关的资料参差不齐,目前尚未有大量相关专门的书籍来介绍该领域的图书或者易懂视频,很多情况需要根据所处应用场景灵活应变。
-推荐刚刚发行的一本关于音频方面的图书《Android音视频开发》
-推荐国内比较专业音视频方面相关的介绍《雷霄骅的专栏》

-
-

附录

-
    -
  • 音频编码格式的比较
  • -
  • 第一行代码
  • -
  • Android MediaRecorder架构详解
  • -
  • 参考代码
  • -
  • 浏览器引擎
  • -
  • 主流浏览器内核介绍
  • -
  • 腾讯X5内核介绍
  • -
  • Android 音视频开发学习思路
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/11/07/cloud-gcp/index.html b/2018/11/07/cloud-gcp/index.html deleted file mode 100644 index 9d3986efe..000000000 --- a/2018/11/07/cloud-gcp/index.html +++ /dev/null @@ -1,720 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Google Cloud Platform for VPN | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Google Cloud Platform for VPN -

- - -
- - - - -

cloud-gcp

-

随着云产品的普及推广,各路国际大场也是纷纷推出了相关云产品的试用,其中具有代表性的Google CloudAmazon,本篇主要讲解Googel Cloud产品的试用,并搭建SSR服务

- -

Google Cloud 特点

-
    -
  • 可使用所有Cloud Platform产品
  • -
  • 免费获得$300赠金
  • -
  • 免费使用结束后不会自动收费
  • -
-

准备

-
    -
  • Google Email
  • -
  • visa 信用卡(需要$1进行认证,认传完成后返还$1)
  • -
-
-

因为Google本身在大陆是无法正常访问的,因此需要先自备梯子,可以先使用Lantern

-
-

GCP

-

申请Google Cloud Platform

-

官网申请https://cloud.google.com/free

-

gcp-register1

-
    -
  • 国家地区:中国
  • -
  • 服务条款:同意
  • -
  • 动态邮件:可选,根据自身需要勾选
  • -
-

gcp-register2

-

根据需要填写一些信息,由于我的Google账号已是开发者账号,一些信息都是完善的,所以Google直接关联了信息,因此也不会再扣除$1,如果你是新账号,详细步骤可参考附录

-

VM创建

-

在创建VM之前,我们先进行网络防火墙修改,避免后续的麻烦

-

gcp-firewall-settings
-gcp-create-firewall
-规则设置如下:
-gcp-firewall-rule

-
    -
  • 名称:自己命名一个用于区分其它得规则
  • -
  • 来源IP地址范围:0.0.0.0/0,这个不要写错
    -其它按照图上设置即可
  • -
-

创建VM实例

-

gcp-create-vm
-gcp-create-vm-init
-gcp-create-vm-course

-
    -
  • 名称:自己写一个即可
  • -
  • 地区:建议选亚洲,别人推荐asia-east1-c台湾彰化县实测延迟低,我这里选择了香港
  • -
  • 机器类型:选微型(1个共享vCPU)
  • -
  • 启动磁盘:推荐CentOS 7,当然也可以其它,选择自己熟悉的系统即可
    -其中关于网络的设置如下:
    -gcp-create-vm-network
  • -
  • 名称:任意输入即可(小写字母开头,不能为大写字母)
  • -
-

设置完成后,创建VM实例

-

连接VM

-

当然,你可以使用浏览器打开连接VM
-gcp-link-vm-chrome

-

经过实际操作,你会发现,在浏览器中操作延迟很高,因此我们就采用其它客户端去连接刚刚创建的这台服务器,下面分别以 Xshell(Windows)iTerm(macOS)来演示如何与 GCP 建立连接

-

使用Xshell

-

密钥生成

-
    -
  1. 新建用户密钥生成向导
    -gcp-link-vm-xshell1
  2. -
  3. 密钥类型长度设置
    -gcp-link-vm-xshell2
  4. -
  5. 生成密钥
    -gcp-link-vm-xshell3
  6. -
  7. 设置密钥名称及密码
    -gcp-link-vm-xshell4
  8. -
  9. 保存密钥
    -gcp-link-vm-xshell5
  10. -
-

GCP添加密钥

-
    -
  • 元数据
    -gcp-link-vm-settings
  • -
  • SSH
    -gcp-link-vm-ssh
  • -
  • SSH密钥添加
    -gcp-link-vm-create-ssh
  • -
-

Xshell 连接服务

-
    -
  • 配置连接的服务器地址
    -gcp-link-vm-ssh-ip
  • -
  • 配置连接服务器的密钥
    -gcp-link-vm-ssh-login
  • -
-

使用 iTerm

-
    -
  1. 添加公钥到 SSH 管理
  2. -
  3. 使用 SSH 命令进行连接
  4. -
-

gcp-item2-ssh

-
-

如果你本地没有已有秘钥,或者你需要生产一对 RSA 新秘钥,可参考MacBook Pro 疑难杂症文章的 免密登录服务器 的前半部分内容

-
-

准备工作

-

内核升级

-
1
2
3
4
5
6
7
8
9
10
# 切换到root用户
sudo -i
# 安装wget
yum install -y wget
# 安装bbr
wget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh
# 给bbr.sh文件设置权限
chmod +x bbr.sh
# 启动bbr.sh脚本
./bbr.sh
-

执行./bbr.sh安装过程如下
-gcp-centos-bbr

-

执行完成后,会提示,输入y回车后重启,这时需要等待几分钟

-

重启完成后,重新连接服务器

-
1
2
3
4
# 切换到root用户
sudo -i
# 查看内核(版本大于4.13或以上版本,就表示OK)
uname -r
-

选择安装服务

-

对于 SSR 和 v2ray 可以提供不可描述的服务,由于v2ray 功能更加强大,并且更加隐蔽,不易被发现,因此极力推荐使用 v2ray 方式

-

SSR

-

通过以上的配置,我们可以使用Xshell进行SSR工具的安装,安装SSR工具前,需要先升级系统内核,按照如下执行命令

-

安装SSR

-
1
2
3
4
5
6
7
8
9
# 切换到root用户
sudo -i
# wget设置
wget --no-check-certificate -O shadowsocks-all.sh
# 下载安装SSR
https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks-all.sh
chmod +x shadowsocks-all.sh
# 执行SSR运行脚本
./shadowsocks-all.sh 2>&1 | tee shadowsocks-all.log
-

安装过程步骤如下:

-
    -
  • 选择版本:推荐ShadowsocksR,输入2
  • -
  • 设置密码
  • -
  • 设置端口
  • -
  • 选择加密方式,这里选择chacha20,输入12
  • -
  • 选择协议,这里选择auth_sha1_v4,输入3
  • -
  • 选择混淆方式,这里选择http_simple,输入2
    -gcp-link-vm-ssh-install
  • -
-

等待安装完成,提示如下:
-gcp-link-vm-ssh-finish

-

根据安装完成后提示的信息配置你的SSR客户端即可

-

修改 SSR 配置

-

在实际过程中,我们安装完成后,可能根据实际环境,需要修改配置,那么我们该怎么去修改呢,直接看下面命令

-
1
2
3
4
5
# shadowsocks-r 默认路径是 /etc/shadowsocks-r
vim /etc/shadowsocks-r/config.json
# 安装实际需要,更改后保存配置,然后重启shadowsocks-r 服务
/etc/init.d/shadowsocks-r restart
# 记得更新你客户端相关的配置
-

卸载 SSR

-
1
2
3
4
5
6
7
8
# 切换到root用户
sudo -i
# 进入脚本目录(可省略)
cd /home/<user-name>/
# 使用 help 查看指令(可省略)
./shadowsocks-all.sh -help
# 执行卸载
./shadowsocks-all.sh uninstall
-
-

当你不知道该应用拥有什么命令时,多用 help 来获取相关的指令

-
-

SSR 常用命令

-
1
2
3
4
5
6
7
8
9
10
# 启动SSR
/etc/init.d/shadowsocks-r start
# 退出SSR
/etc/init.d/shadowsocks-r stop
# 重启SSR
/etc/init.d/shadowsocks-r restart
# SSR状态
/etc/init.d/shadowsocks-r status
# 卸载SSR,默认目录 /home/<user-name>/
./shadowsocks-all.sh uninstall
-

v2ray

-

时间校准

-

对于 v2ray,它的验证方式包含时间,就算是配置没有任何问题,如果时间不正确,也无法连接 v2ray 服务器的,服务器会认为你这是不合法的请求。所以系统时间一定要正确,只要保证时间误差在90秒之内就没问题

-
1
2
3
4
5
6
7
8
# 查看 VPS 时间
date -R
# +0000表示时区
Sat, 23 Nov 2019 17:21:50 +0000
# 将系统服务时间设置成本地时间
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 检查服务器系统时间是否和本地时间一样
date -R
-

安装 v2ray

-
1
2
3
4
5
6
# 安装脚本
bash <(curl -L -s https://install.direct/go.sh)
# 安装完成,v2ray 并不会自动运行,因此需要手动启动服务
sudo systemctl start v2ray
# 查看 v2ray 运行状态
service v2ray status
-

基本文件安装路径地址

-
    -
  • /usr/bin/v2ray/v2ray:V2Ray 程序;
  • -
  • /usr/bin/v2ray/v2ctl:V2Ray 工具;
  • -
  • /etc/v2ray/config.json:配置文件;
  • -
  • /usr/bin/v2ray/geoip.dat:IP 数据文件
  • -
  • /usr/bin/v2ray/geosite.dat:域名数据文件
  • -
-

安装过程截图如下
-gcp-v2ray-install

-

配置修改

-

客户端连接

-

问题排查

-

每到敏感时期,一大批服务都会被封,这次我的服务也不理外,这里就讲一讲我是如何排除问题。

-
-

所处环境说明:
-0. VPS 上安装的 SSR 服务

-
    -
  1. 可以使用 SSH 工具(比如:Xshell)可以连接 VPS 机器
  2. -
  3. 在 VPS 中 ping google.com 是可以的
  4. -
  5. 连接 VPS 的客户端,无法正常访问国外网站
  6. -
-
-

出现以上情况,大概率是当前的 SSR 服务端口被封了,可以通过以下方式来验证

-
    -
  1. -

    使用国内站长工具端口扫描检查下 VPS 的端口是否可以正常访问,地址:http://tool.chinaz.com/port

    -
      -
    • 如果提示关闭,说明国内无法访问该 VPS 对应的端口服务
    • -
    • 如果提示开启,说明访问正常
    • -
    -
  2. -
  3. -

    使用用国外端口扫描网站进行检查你的 VPS 服务是否可以正常访问,地址:https://www.yougetsignal.com/tools/open-ports

    -
      -
    • 如果检查结果为open,说明国外可以正常访问你的 VPS 对应端口的服务
    • -
    • 如果检查结果为close,说明国外无法访问
    • -
    -
  4. -
-

经过上面的两部,可以快速定位到问题,如果你检查出来的结果一样,则可以更改 VPS 上的 SSR 服务端口,重新启动 SSR 服务即可,在上面已经讲到了如何修改 SSR 配置,切记一起连加密方式
-协议混淆方式这些配置一并改掉,然后重启 SSR 服务,并修改连接 SSR 服务的客户端配置,其实这只是一个暂时的解决方法,我们可以使用更加隐蔽的 v2ray 服务

-

其它

-
    -
  • 查询余额
    -进入结算概览页面: https://console.cloud.google.com/billing/
  • -
  • 扣费计算
    -主机:$5/月.
    -流量:谷歌云服务器出口大陆流量1T以内价格约为0.23$/1G.
    -每个月可用流量:$300-$5*12=$240/12/0.23 ≈ 86G
  • -
  • SSR客户端
  • -
-

附录

-
    -
  • Google Cloud Platform免费申请&一键搭建SSR & BBR加速教程
  • -
  • Google Cloud使用VM虚拟机详细操作指南
  • -
  • ShadowsocksR客户端 各种隐藏使用技巧说明
  • -
  • v2ray社区:https://www.v2ray.com已被墙https://www.v2fly.org
  • -
  • 自建v2ray服务器教程
  • -
  • v2ray各平台图文使用教程
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/11/10/mac-init/index.html b/2018/11/10/mac-init/index.html deleted file mode 100644 index fee555c41..000000000 --- a/2018/11/10/mac-init/index.html +++ /dev/null @@ -1,637 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -MacBook Pro 初始化 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- MacBook Pro 初始化 -

- - -
- - - - -

今天拿到了一辆跑车 MBP,虽然不是顶配,也能算上中等吧,废话不啰嗦,上来就是一顿操作猛如虎,最终效果就是唬

-

跑车的一些零配件来源地Awesome MacMacWK 一些破解软件集合地

-

软件的安装,这里不再赘述,这里主要对常用开发软件的配置进行记录

- -

JDK

-

作为Android开发者,JDK的安装那是少不了

-

下载

-

在Oracle 官网下载所需JDK 版本,这里举例:JDK1.8.0_191

-

安装

-

此处省略,简单的安装步骤

-

配置

-

以下命令相关操作,均在自带系统终端应用或者自己安装的其他终端命令工具

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看安装的Java版本
java -version
# 编辑profile文件
sudo vim /etc/profile
# 在打开的 profile 文件中,最下面加入以下文本,添加完成后,保存退出
JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/"

CLASS_PATH="$JAVA_HOME/lib"

PATH=".:$PATH:$JAVA_HOME/bin"

# 使配置生效
source /etc/profile
# 查看JAVA_HOME是否正确
echo $JAVA_HOME
-

jdk-command

-

jdk-config

-
-

注意:

-
    -
  1. JAVA_HOMEjdk1.8.0_191.jdk 是自己安装对应版本的文件夹,可以在Finder中,快捷键:Command + Shift + G,输入: /Library/Java/JavaVirtualMachines/ ,最终得到对应的文件夹名,如: jdk1.8.0_191.jdk
  2. -
  3. vim模式下,输入“i”:表示,插入,“esc”:表示退出编辑模式,“:wq!”:表示保存并退出
  4. -
-
-

MySQL

-

下载

-

官方下载地址:https://dev.mysql.com/downloads/

-

mac-mysql-downlaod

-

安装

-

mac-mysql-install

-

我这里选择自定义设置密码,请记住你设置的密码,最好是大小写+数字+字符的组合方式

-

登录

-
1
2
3
# 使用 root 账号登录 MySQL
mysql -u root -p
# 出现下图中的MySQL,表示成功连接 MySQL
-

mac-login-mysql

-

卸载

-

mac-mysql-uninstall

-

Git

-

直接在自带系统终端应用中,输入 git --version ,由于之前并没有安装,系统会提示,直接同意并安装即可

-

GitHub配置

-
1
2
3
4
5
6
7
# 查看本地是否生成过秘钥,如果该文件夹不存在,则表示未生成过秘钥
cd ~/.ssh
# 生成一个github的秘钥,这里github可以根据喜好自己命名(默认.ssh路径,不添加密码等操作,直接三次回车,即可生成秘钥)
ssh-keygen -t rsa -C "github"
# 查看公钥
cat ~/.ssh/id_rsa.pub
# 复制公钥添加到GitHub的SSH设置中,这里省略操作截图步骤
-

mac-github-config

-

Gradle配置

-
    -
  • -

    下载地址:官网,下载-all版本

    -
  • -
  • -

    设置GRADLE_HOME路径

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 打开.bash_profile文件
    open -e .bash_profile
    # Gradle_HOME环境设置,并保存
    GRADLE_HOME=/Users/blade/Documents/DevTools/Gradle/gradle-4.6
    export GRADLE_HOME
    export PATH=$PATH:$GRADLE_HOME/bin
    # 配置文件生效
    source ~/.bash_profile
    # 验证配置
    gradle -version
    -
    -

    如果提示The file /Users/blade/.bash_profile does not exist.则在根路径下创建 .bash_profile 文件
    -执行命令 touch .bash_profilesss

    -
    -

    gradle-config

    -
  • -
-

Keka

-

对于 macOS 上的文件解压缩工具,有The UnarchiverBetterZipKeka等,我这里使用 keka,毕竟开源:https://github.com/aonez/Keka,真香

-
1
2
3
4
# 安装
brew cask install keka
# 卸载
brew cask zap keka
-
-

Mac 压缩 / 解压缩工具解决方案

-
-

OpenInTerminal

-

OpenInTerminal 是在 Finder 上的一个扩展工具,能够快速在当前位置已命令行或者指定的编辑器打开,非常方便,对于 OpenInTerminal 和 OpenInTerminal-Lite,OpenInEditor-Lite 的区别

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FeaturesOpenInTerminalOpenInTerminal-Lite & OpenInEditor-Lite
Support Terminal, iTerm, Hyper, Alacritty and kitty.
Support TextEdit, Visual Studio Code, VSCode Insiders, Atom, Sublime Text, VSCodium, BBEdit, TextMate, CotEditor, MacVim and JetBrains(AppCode, CLion, GoLand, IntelliJ IDEA, PhpStorm, PyCharm, RubyMine, WebStorm).
Set to open a new tab or window.
Support English, Chinese, French, Russian, Italian and Spanish.
Copy path of the selected file or Finder window to Clipboard
GUI preferences
Support keyboard shortcuts.
Support Dark Mode.
-

安装

-
    -
  • openinterminal
    1
    brew cask install openinterminal
    -
  • -
  • openinterminal-lite & openineditor-lite
    1
    2
    brew cask install openinterminal-lite
    brew cask install openineditor-lite
    -
  • -
-

配置

-

这里主要是对应用进行授权

-
    -
  • openinterminal
    -System Preferences(系统偏好设置) -> Extensions(扩展) -> Finder Extensions(访达扩展)
  • -
  • openinterminal-lite & openineditor-lite
    -拖拽 openinterminal-lite & openineditor-lite 到你的 Finder 的状态栏上
  • -
-

macOS 快捷键

-

顺带记录自己常用的快捷键吧🙃,其他用的时候在边用边查找吧

-
-

Mac 键盘快捷键

-
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/11/24/mac-bash/index.html b/2018/11/24/mac-bash/index.html deleted file mode 100644 index a7c530edb..000000000 --- a/2018/11/24/mac-bash/index.html +++ /dev/null @@ -1,845 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -应该知道的系统环境配置文件 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 应该知道的系统环境配置文件 -

- - -
- - - - -

在计算机操作系统中Shell是用户与操作系统交互的媒介,而bash作为目前Linux\macOS系统中最常用的Shell,它支持的startup文件也并不单一,甚至让人感到费解,以下就是对Shell的学习

-

Shell:在计算机中,值“为用户提供用户界面”的软件,通常指的是 命令行界面 的解析器。一般来说,Shell指操作系统中提供访问内核所提供的服务程序。

- -

通常将Shell分为两类

-
    -
  • 命令行:提供一个命令行界面(CLI)
  • -
  • 图形界面:提供一个图形用户界面(GUI)
  • -
-

linux_system

-

在PC桌面领域,不同的操作系统都有自己的Shell,截止2018.10主流的操作系统市场占有率,Windows(78.04%),OS X(13.73%),Unknown(5.44%),Linux(1.64%),Chrome(1.15%),数据来源于statcounter

-

这些操作系统中都有自己独特的Shell命令,在不同的系统版本中,命令工具也是不完全相同,例如:

-
    -
  • Windows:Windows CE、Windows NT常用cmd.exe;Windows 10中常用PowerShell
  • -
  • OS X:默认bash,除此之外还提供了tcshzshksh
  • -
  • Linux:/etc/shells路径下,/bin/sh/bin/bash/bin/csh等应用
  • -
-
-

更详细的请查阅维基百科

-
-

Configuration Files

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
文件shkshcshtcshbashzsh
/etc/.loginloginlogin
/etc/csh.cshrcyesyes
/etc/csh.loginloginlogin
~/.tcshrcyes
~/.cshrcyesyes
~/etc/ksh.kshrcint.
/etc/sh.shrcint.
$ENV (typically ~/.kshrc)int.int.int.
~/.loginloginlogin
~/.logoutloginlogin
/etc/profileloginloginloginlogin
~/.profileloginloginloginlogin
~/.bash_profilelogin
~/.bash_loginlogin
~/.bash_logoutlogin
~/.bashrcint.+n/login
/etc/zshenvyes
/etc/zprofilelogin
/etc/zshrcint.
/etc/zloginlogin
/etc/zlogoutlogin
~/.zshenvyes
~/.zprofilelogin
~/.zshrcint.
~/.zloginlogin
-
    -
  • yes:表示shell在启动时始终读取文件
  • -
  • login:表示如果shell是登录shell,则读取文件
  • -
  • n/login:表示如果shell不是登录shell,则读取文件
  • -
  • int.:表示如果shell是交互式的,则读取文件
  • -
-
-

更详细的介绍请查阅维基百科

-
-

关于常用Shell,执行流程如下图:
-flow

-

startup文件

-

bash作为目前LinuxmacOS(默认bash命令)系统中最常用的shell,通过上面的表格,我们可以知道macOS系统中,bash主要由以下文件

-
    -
  • /etc/profile:The systemwide initialization file, executed for login shells
  • -
  • ~/.profile:
  • -
  • ~/.bash_profile:The personal initialization file, executed for login shells
  • -
  • ~/.bash_login:
  • -
  • ~/.bash_logout:The individual login shell cleanup file, executed when a login shell exits
  • -
  • ~/.bashrc:The individual per-interactive-shell startup file
  • -
-

我们看看在macOS系统中,bash的startup文件是如何进行加载

-

注意:

-
    -
  • /etc/profile/etc/paths是系统级别,系统启动后就会加载,后面的配置文件是当前用户级的环境变量
  • -
  • 如果~/.bash_profile存在,后面几个文件就会忽略不读,不在时,才会以此类推读取后面的文件
  • -
  • ~/.bashrc没有上述规则,他始终加载,它是在bash shell打开的时候载入的
  • -
-
-

特点

-

bash的两种属性,即 “交互”“登录”,按照bash是否与用户进行交互,可将其分为 “交互式”“非交互式”;按照bash是否被用户登录,又可将其分为 “登录shell”“非登录shell”

-

交互式与非交互式

-
    -
  • 交互式:shell的一种运行模式,交互式shell等待用户输入命令,并且立即执行,然后将结果反馈给用户。整个流程:登录——>执行命令——>退出。当你退出后,这个shell就终止
  • -
  • 非交互式:shell的另一种运行模式,它专门用来执行预先设定的命令。这种模式下,shell不予用户进行交互,而是读取存储在脚本文件中的命令并执行它们。当它读取到文件结尾,这个shell就终止
  • -
-

登录shell与非登录shell

-
    -
  • 登录shell: -
      -
    • 用户通过输入用户名/密码(或者证书认证)后启动的shell;
    • -
    • 通过带有-l|--login参数的bash命令启动的shell
      -例如:系统启动,远程启动,使用su -切换用户,通过bash --login命令启动的bash等
    • -
    -
  • -
  • 非登录shell:以上情况除外基本就是 “非登录shell”
    -例如:从图形化界面启动终端,使用su -切换用户,通过bash命令启动bash等
  • -
-

主要区别

-
    -
  • 使用logout退出登录shell,使用exit退出非登录shell
  • -
  • 其实exit命令会判断当前shell的登录属性,并且分别调用logoutexit指令
  • -
  • 登录shell非登录shell的主要区别在于启动shell时所执行的startup文件不同;登录shell执行的startup文件为~/.bash_profile,而非登录shell执行的startup文件为~/.bashrc
  • -
-

总结

-

Path语法

-
1
2
# 中间使用冒号分隔
export PATH=$PATH:<PATH 1>:<PATH 2>:<PATH 3>:------:<PATH N>
-

环境变量设置

-

全局设置

-
    -
  • /etc/paths:全局环境变量设置,建议修改此文件
  • -
  • /etc/profile:不建议修改此文件,全局配置,不管是哪个用户,登录时都会读取此文件
  • -
  • /etc/bashrc:一般在这个文件中添加系统级别环境变量,全局配置,bash shell执行时,不管是何种方式,都会读取此文件
  • -
-

单用户设置

-
    -
  • ~/.bash_profile:添加用户级环境变量
    -例如:设置ANDROID_HOME到PATH
    1
    2
    export ANDROID_HOME=/Users/shaoc/Library/Android/sdk
    export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH
    -
  • -
  • ~/.bashrc 同上
    -一般重启shell设置就会生效,如果想立刻生效,则可执行下面的语句:
    1
    source 相应的文件
    -
  • -
-

zsh中配置环境变量

-

在安装 oh my zsh后,.bash_profile文件中的环境变量就无法起到作用,因为终端默认启动的是zsh,而不是shell,所以无法加载

-
    -
  • -

    解决方法
    -在~/.zshrc配置文件中,增加对.bash_profile的引用:

    -
    1
    source ~/.bash_profile
    -

    .bash_profile文件示例:

    -
    1
    2
    3
    4
    export ANDROID_HOME=/Users/blade/Library/Android/sdk
    export GRADLE_HOME=/Users/blade/Documents/DevTools/Gradle/gradle-4.6
    export FLUTTER_HOME=/Users/blade/Documents/DevTools/flutter
    export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$GRADLE_HOME/bin:$FLUTTER_HOME/bin:$PATH
    -
  • -
-

附录

-
    -
  • 原关于“.bash_profile”和“.bashrc”区别的总结
  • -
  • Mac环境变量配置
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/11/29/charles/index.html b/2018/11/29/charles/index.html deleted file mode 100644 index d4969a678..000000000 --- a/2018/11/29/charles/index.html +++ /dev/null @@ -1,665 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Charles 使用教程 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Charles 使用教程 -

- - -
- - - - -

-

Charles is an HTTP proxy / HTTP monitor / Reverse Proxy that enables a developer to view all of the HTTP and SSL / HTTPS traffic between their machine and the Internet. This includes requests, responses and the HTTP headers (which contain the cookies and caching information)

- -

Charles是一个HTTP代理/ HTTP监视器/ 反向代理,使开发人员能够查看其机器和Internet之间的所有HTTP和SSL / HTTPS流量,这包括请求,响应和HTTP标头(包含cookie和缓存信息)

-

Charles

-

主要特点

-
    -
  • SSL代理 - 以纯文本格式查看SSL请求和响应
  • -
  • Bandwidth Throttling模拟较慢的Internet连接,包括延迟
  • -
  • AJAX调试 - 以树或文本形式查看XML和JSON请求和响应
  • -
  • AMF - 以树形式查看Flash Remoting / Flex Remoting消息的内容
  • -
  • 重复请求以测试后端更改
  • -
  • 编辑测试不同输入的请求
  • -
  • 用于拦截和编辑请求或响应的断点
  • -
  • 使用W3C验证器验证记录的HTML,CSS和RSS / atom响应
  • -
-
-

本篇文章操作均基于Charlers 4.2.8版本,及 macOS 10.14.5 版本

-
-

安装

-
    -
  • Windows:略
  • -
  • macOS:略
  • -
-
-

下载地址:官方Charles

-
-

激活

-

有能力,请支持付费支持正版~
-有能力,请支持付费支持正版~
-有能力,请支持付费支持正版~

-

仅供个人学习研究和交流使用,请勿用于任何商业用途。
-Charles ——> Help ——> Register Charles…

-
1
2
Registered Name: https://zhile.io
License Key: 48891cf209c6d32bf4
-

激活密钥来自知了

-

配置

-

配置流程

-
    -
  1. 获取操作系统网络IP地址
  2. -
  3. 修改客户端网络IP连接
  4. -
  5. 在操作系统及客户端上安装证书
  6. -
  7. 设置SSL代理
  8. -
-
-

以上配置要求,操作系统(Windows,macOS)及客户端(Android,iOS)连接在 同一WiFi网络

-
-

获取系统 IP

-

不管是是 Windows 系统还是 macOS 系统都可以通过 Charles 来获取,获取方式 Help ——> Local IP Address
-charles-ip

-
Windows
-

使用命令行查看网络 IP 地址 ipconfig
-charles-windows-ip

-
macOS
-
    -
  • 使用命令行查看网络 IP 地址 ifconfig en0
    -charles-mac-ip
  • -
  • macOS系统设置查看IP 地址 System Preferences ——> Network
    -charles-network-ip
  • -
-

查看监听端口

-

Proxy——> Proxy settings...
-charles-view-port

-

客户端设置

-

要求手机网络与 PC 网络同链接在一个路由器网络下,这样手机的请求都将通过 PC,因此在 Charles 上可以看到手机上的网络请求。

-

手机上安装下面的步骤请看下面的详细介绍,安装完证书,Charles 将会收到提示,进行允许即可
-charles-allow

-
Android
-
    -
  • -

    修改网络配置选项

    -
  • -
  • -

    导入Charles证书,使用浏览器打开 www.charlesproxy.com/getsslhttp://chls.pro/ssl,下载证书,并进行安装

    -
  • -
-
iOS
-
    -
  • -

    修改网络配置选项

    -
  • -
  • -

    导入Charles证书

    -
  • -
  • -

    证书授权

    -
  • -
-
模拟器
-

Charles设置

-
    -
  • Install Charles Root Certificate
    -完成客户端的设置,我们此时再对 Charles 进行设置,首先我们先进性安装 Charles 证书,Help ——> SSL Proxying... ——> Install Charles Root Certificate
    -charles-install-system
  • -
  • 添加证书
    -charles-add
  • -
  • 证书授权设置
    -charles-ca-settings
  • -
  • SSL Proxy settings
    -charles-ssl-settings -
      -
    • Host:为需要过滤的域名地址,* 表示不过滤
    • -
    • Port:固定为443,* 表示任意端口
    • -
    -
  • -
-

抓包

-

不废话,请看图
-charles-overview

-
    -
  • Structure:视图将网络请求按访问的域名分类
  • -
  • Sequence:视图将网络请求按访问的时间排序
  • -
-
-

客户端请不要开启其他代理

-
-

进阶

-

过滤网络请求

-
    -
  • 方法一:在上面抓包的截图中,已经讲过,适用于 临时型 对请求进行过滤
  • -
  • 方法二:Proxy ——> Recording settings ——> include ,适用于 经常性 请求过滤
    -charles-filter-often
  • -
-

Map 功能

-

设置本地映射

-

指的是将网络请求重定向到本地的文件,适用于开发过程中,把线上的静态资源映射到本地,这样可以方便调试并及时查看效果,确定无误后再发布到线上环境
-charles-map-local

-

设置远程映射

-

指的是将网络请求重定向到另一个网络请求地址,适用于开发过程中,需要将请求重定向到其他的服务上
-charles-map-remote

-

重复发送网络请求

-

可以更加需要重复一次或多次的请求,对于多次的请求可用于服务器的压力测试
-charles-repeat

-

常见问题

-
    -
  1. Charles无法抓取到客户端网络请求 -
      -
    • 查看你的客户端网络设置,是否正确
    • -
    • 查看你的客户端和 Charles 是否是处于同一网络环境
    • -
    -
  2. -
  3. Charles 无法抓取 Https 网络请求 -
      -
    • 查看你的客户端和 Charles 是否安装证书,并设置终是允许
    • -
    -
  4. -
  5. 网络请求及网络响应信息中文乱码
  6. -
  7. 如果需要抓本地应用(即拦截电脑上的应用接口),请确保你未开启翻墙代理软件
  8. -
-

附录

-
    -
  • Charles Document
  • -
  • Charles抓包的安装,使用说明以及常见问题解决
  • -
  • 抓包工具Charles的使用心得
  • -
  • Charles抓包https
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/12/12/mac-download/index.html b/2018/12/12/mac-download/index.html deleted file mode 100644 index 54dd38c6a..000000000 --- a/2018/12/12/mac-download/index.html +++ /dev/null @@ -1,537 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Aria2 之 macOS | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Aria2 之 macOS -

- - -
- - - - -

Aria2 是什么

-

Aria2 是一款支持多种协议的 轻量级命令行 下载工具。有以下特性:

-
    -
  • 多线程连线:Aria2 会自动从多个线程下载文件,并充分利用你的带宽;
  • -
  • 轻量:运行时不会占用过多资源,根据官方介绍,内存占用通常在 4MB~9MB,使用 BitTorrent 协议,下行速度 2.8MB/s 时 CPU 占用率约 6%;
  • -
  • 全功能 BitTorrent 客户端;
  • -
  • 支持 RPC 界面远程控制
  • -
- -

Aria2 安装

-
1
brew install aria2
-
-

Homebrew是一款自由及开放源代码的软件包管理系统,用以简化Mac OS X系统上的软件安装过程,以Ruby语言写成,默认安装在/usr/local

-
-

Aria2 配置

-
1
2
3
4
5
6
# 进入~路径
cd ~
# 创建.aria2文件夹
mkdir .aria2
# 创建aria2.conf配置文件
touch aria2.conf
-

复制以下内容保存在aria2.conf文件中,修改 dir=/Users/blade/Downloads路径即可

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#用户名
#rpc-user=user
#密码
#rpc-passwd=passwd
#上面的认证方式不建议使用,建议使用下面的token方式
#设置加密的密钥
#rpc-secret=token
#允许rpc
enable-rpc=true
#允许所有来源, web界面跨域权限需要
rpc-allow-origin-all=true
#允许外部访问,false的话只监听本地端口
rpc-listen-all=true
#RPC端口, 仅当默认端口被占用时修改
rpc-listen-port=6800
#最大同时下载数(任务数), 路由建议值: 3
max-concurrent-downloads=5
#断点续传
continue=true
#同服务器连接数
max-connection-per-server=5
#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要
min-split-size=10M
#单文件最大线程数, 路由建议值: 5
split=10
#下载速度限制
max-overall-download-limit=0
#单文件速度限制
max-download-limit=0
#上传速度限制
max-overall-upload-limit=0
#单文件速度限制
max-upload-limit=0
#断开速度过慢的连接
#lowest-speed-limit=0
#验证用,需要1.16.1之后的release版本
#referer=*
#文件保存路径, 默认为当前启动位置
dir=/Users/blade/Downloads
#文件缓存, 使用内置的文件缓存, 如果你不相信Linux内核文件缓存和磁盘内置缓存时使用, 需要1.16及以上版本
#disk-cache=0
#另一种Linux文件缓存方式, 使用前确保您使用的内核支持此选项, 需要1.15及以上版本(?)
#enable-mmap=true
#文件预分配, 能有效降低文件碎片, 提高磁盘性能. 缺点是预分配时间较长
#所需时间 none < falloc ? trunc << prealloc, falloc和trunc需要文件系统和内核支持
file-allocation=prealloc
-

开启 Aria2

-

终端中输入,其中xxx是你的电脑用户名

-
1
aria2c --conf-path="/Users/xxx/.aria2/aria2.conf" -D
-

Aria2 开机自启

-
    -
  1. 创建aria2.plist文件
    1
    2
    cd ~/Library/LaunchAgents
    touch aria2.plist
    -
  2. -
  3. 修改aria2.plist文件内容,其中<array></array>中的值改为自己电脑上 aria2c 命令的路径,可以在终端输入which aria2c查看,将WorkingDirectory后面的<string></string>中的值改为自己的下载路径
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?xml version="1.0"encoding="utf-8"?>
    <!DOCTYPE plist PUBLIC"-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    <key>KeepAlive</key>
    <true />
    <key>RunAtLoad</key>
    <true />
    <key>Label</key>
    <string>aria2</string>
    <key>ProgramArguments</key>
    <array>
    <string>/usr/local/bin/aria2c</string>
    </array>
    <key>WorkingDirectory</key>
    <string>/Users/blade/Downloads</string>
    </dict>
    </plist>
    -
  4. -
-

启用Web

-

其实,如果你喜欢使用命令来操作,那么此步可跳过

-
1
2
3
4
5
# 获取项目代码
git clone https://github.com/ziahamza/webui-aria2
# 打开 index.html 文件
cd webui-aria2/docs
open index.html
-

其他

-

进行brew更新警告

-

警告内容:Unbrewed header files were found in /usr/local/include ...
-原因:系统中已存在下面列表中包含的包内容不是通过brew进行安装
-解决方法:删除那些文件就可以了

-
1
2
3
4
# 或者获取sudo权限删除
sudo rm -rf ‘/usr/local/bin/node’
# 重新安装node
brew install node
-

附录

-
    -
  • Mac安装使用aria2,AriaNg下载百度网盘资源
  • -
  • 如何配置 Aria2 来进行文件下载
  • -
  • Aria2 - 下载神器
  • -
  • Aria2 命令使用参考文档
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/12/16/flutter-init/index.html b/2018/12/16/flutter-init/index.html deleted file mode 100644 index a0b584373..000000000 --- a/2018/12/16/flutter-init/index.html +++ /dev/null @@ -1,562 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Flutter(一)之环境搭建 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Flutter(一)之环境搭建 -

- - -
- - - - -

这两年随着前端的高速发展,大前端的趋势下,Native移动应用开发市场在一定程度上被前端瓜分,加之硬件的快速迭代,性能已不存在明显的短板,React NativeVueAngular等等这些Web框架,对移动端也有了较大的提升,毕竟这样的开发效率会直线上升,并且大大减少了成本。技术的革新真的好快,如果不去学习,很快就会被淘汰

-

那就直接进入正题,flutter是一站式跨平台解决方案,一次开发,适配整个移动平台,并且是由Google进行主导开发,开源的一个项目,现如今已经迭代到1.0版本

-

本篇文章主要记录在macOS系统上搭建flutter开发环境的过程

- -

准备

-
    -
  • Android Studio开发环境(JDK,AndroidSDK,Gradle等等,这里不再赘述)
  • -
  • flutter SDK
  • -
  • Android Studio Plugin --> Flutter
  • -
-

步骤

-
    -
  1. -

    解压下载的flutter SDK,并配置环境变量,例如这里配置在.bash_profile文件中

    -
    1
    2
    3
    4
    5
    6
    7
    # 打开 .bash_profile文件
    vim .bash_profile
    # .bash_profile文件中加入flutter sdk路径并保存
    export FLUTTER_HOME=/Users/blade/Documents/DevTools/flutter
    export PATH=$FLUTTER_HOME/bin:$PATH
    # 重新加载.bash_profile文件
    source .bash_profile
    -
  2. -
  3. -

    检查环境变量是否配置正确,如果有相关命令说明,表示已配置好环境变量

    -
    1
    flutter -h
    -
  4. -
  5. -

    检查开发环境,第一次执行,应该提示如下图所示说明

    -
    1
    flutter doctor
    -

    flutter-doctor
    -其实不难,看出我们需要安装一下其他辅助工具等

    -
  6. -
  7. -

    解决问题,按照如下命令,一步步执行,大概得1个小时左右(取决于你的网络情况)

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 允许协议(android-licenses
    flutter doctor --android-licenses
    # 安装libimobiledevice
    brew install --HEAD libimobiledevice
    # 安装ideviceinstaller
    brew install ideviceinstaller
    # 安装ios-deploy
    brew install ios-deploy
    # 安装cocoapods
    brew install cocoapods
    # cocoapods 初始化,这一步比较耗时,需要下载文件大致547M,需要耐心等待
    pod setup
    -
  8. -
  9. -

    以上步骤都正常运行后,再次检查环境,如下图所示结果,表示已完成flutter环境搭建

    -
    1
    flutter doctor
    -

    flutter-finish

    -
  10. -
-

辅助

-

如果你不习惯或者不想使用Android Studio来开发Flutter,那么使用VS Code是最佳推荐的文本编辑器,只需要在VS Code中安装Flutter插件即可,它已包含所需的Dart语法插件

-

关于程序的运行,那么模拟器当然少不了,这里介绍下macOS上如何启动Android 模拟器

-
    -
  • 首先AndroidSDK的环境变量配置少不了
  • -
  • 配置emulator
    1
    2
    3
    export ANDROID_HOME=/Users/blade/Library/Android/sdk
    export FLUTTER_HOME=/Users/blade/Documents/DevTools/flutter
    export PATH=$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$FLUTTER_HOME/bin:$PATH
    -
  • -
  • 启动
    1
    2
    3
    4
    # 查看已创建模拟器清单
    emulator -list-avds
    # 选择需要启动的模拟器,avd_name:表示从上面列表获取到的模拟器名称
    emulator -avd [avd_name]
    -
  • -
-

注意:

-
    -
  • 不推荐使用Genymotion,flutter的运行在此模拟器上有各种灵异bug
  • -
  • PANIC: Missing emulator engine program for ‘x86’ CPU.解决方式:创建一个x64的模拟器
  • -
-
-

问题

-

libusbmuxd version error during flutter install

-
1
2
3
4
5
6
7
brew update
brew uninstall --ignore-dependencies libimobiledevice
brew uninstall --ignore-dependencies usbmuxd
brew install --HEAD usbmuxd
brew unlink usbmuxd
brew link usbmuxd
brew install --HEAD libimobiledevice
-

Unbrewed header files were found in /usr/local/include

-

flutter-node

-

附录

-
    -
  • flutter docs
  • -
  • Flutter免费视频第一季-环境搭建
  • -
  • flutter安装记录过程
  • -
  • macOS上搭建Flutter开发环境
  • -
  • 官方命令行构建您的应用
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/01/02/idea-skill/index.html b/2019/01/02/idea-skill/index.html deleted file mode 100644 index 3485c3d9a..000000000 --- a/2019/01/02/idea-skill/index.html +++ /dev/null @@ -1,576 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -开发小技巧 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 开发小技巧 -

- - -
- - - - -

Android Studio 是Google基于JetBrains的 IntelliJ IDEA 所定制开发的 Android 开发 IDE。因此这里的设置适用于 JetBrains 公司系列的开发工具,同样也适用于 Android Studio,这是一篇持续更新的文章,在平时的使用过程中一些习惯性的模板化的一些设置,可以减少我们一些重复性的操作,进而提高开发效率。

- -

设置

-

快捷键:

-
    -
  • Windows:Ctrl+Alt+S
  • -
  • macOS:+,
  • -
-

样式

-

约束提示/空格及缩进

-
    -
  • 描述: -
      -
    • 为了约束编写的代码过长而不换行,在代码编辑面板右侧右侧有个条竖线进行约束和警示,当然你可以关闭
    • -
    • 为了工整的显示代码的空格和换行是否正确,可以开启显示空格和缩进等样式
    • -
    -
  • -
-

idea-skill-line

-

窗口打开全部展示

-
    -
  • 描述:为了在编辑器中展示全部打开的文件(不限制在同一行)
    -idea-open-windows-limit
  • -
-

颜色

-

局部变量

-
    -
  • 描述:为了直观的区分出全局变量和局部变量,而不需要仔细阅读代码
    -idea-skill-local
  • -
-

控制台日志

-
    -
  • 描述:为了直观在控制台上显示不同级别日志
    -idea-logcat-color
  • -
-

颜色推荐:

-
    -
  • Assert:AA66CC
  • -
  • Debug:33B5E5
  • -
  • Error:FF6B68
  • -
  • Info:99CC00
  • -
  • Verbose:BBBBBB
  • -
  • Warning:FFBB33
  • -
-

其他

-

Toolbar添加设置按钮

-
    -
  • 描述:在不方便使用快捷键打开设置时,原本的操作是:File–>Settings Repository…,因此调整到状态栏上
    -idea-skill-settings
  • -
-

不区分大小写

-
    -
  • 描述:在编码过程中,通常一些智能提示需要根据输入的支付来提示,而大小写不同对应的提示也不完全一致,因此取消智能提示对大小写字符的要求
    -idea-skill-case
  • -
-

自动导包

-
    -
  • 描述:在编码过程中,一些无用或者需要引入的包,可设置成自动的方式,当无法自动导入或移除无用包时,再手动的去选择处理
    -idea-skill-auto
  • -
-

字段默认前缀

-
    -
  • 描述:为了让代码规范,我们会对变量前面设置默认前缀,那么 idea 也是支持
    -idea-field-prefix
  • -
-

常用技巧

-

编码技巧

-

调试技巧

-

参考

-
    -
  • IntelliJ-IDEA-Tutorial
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/01/10/docker-init/index.html b/2019/01/10/docker-init/index.html deleted file mode 100644 index f715baad2..000000000 --- a/2019/01/10/docker-init/index.html +++ /dev/null @@ -1,492 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Docker 之 SpringBoot 项目部署 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Docker 之 SpringBoot 项目部署 -

- - -
- - - - -
-
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/01/10/idea-multi-module/index.html b/2019/01/10/idea-multi-module/index.html deleted file mode 100644 index 42bb7ee2d..000000000 --- a/2019/01/10/idea-multi-module/index.html +++ /dev/null @@ -1,596 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -IDEA 多模块项目 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- IDEA 多模块项目 -

- - -
- - - - -

Jetbrains系列中IDEA是现如今公认最好用,最强大的Java开发工具,不接受任何反驳,本篇介绍macOS上使用 IDEA 创建 SpringBoot 多模块项目

-

准备工作

-
    -
  • 系统环境:macOS 10.14.2
  • -
  • 应用工具:IDEAMaven
  • -
-
-

这里不再介绍基本软件的安装及配置

-
- -

多模块项目

-

一般简单的项目,按照如下项目结构进行构建,可根据也无需要自行调整

-
1
2
3
4
5
6
7
rc-springboot-docker
├── boot-api # 项目对应用服务间提供api的接口,同时也管理项目常量、REST返回组装实体类等
├── boot-common # 项目公共基础包(可丢弃)
├── boot-core # 项目业务操作,server dao层
├── boot-web # 项目后端Web管理
├── boot-rest # 项目业务控制层,给客户端提供rest接口
└── README.md
-
    -
  • boot-api:是一个maven module
  • -
  • boot-common:是一个maven module
  • -
  • boot-core:是一个maven module
  • -
  • boot-web:是一个springboot module
  • -
  • boot-rest:是一个springboot module
  • -
-

构建

-

Parent Project

-

顾名思义,这是项目的外壳,一个标准的empty maven project,当然你要可以使用gradle来作为项目的构建工具,可根据自身需要自行选择,这里采用maven方式演示

-
    -
  • -

    Create Project
    -idea-new-project

    -
  • -
  • -

    设置项目groupId和artifactId等信息
    -idea-new-setting

    -
  • -
  • -

    设置项目名称及项目存储位置
    -idea-new-path

    -
  • -
  • -

    删除项目src目录,使项目成为名副其实的空项目
    -idea-delete-src

    -
  • -
  • -

    新增忽略文件
    -idea-new-ignore
    -新增忽略文件的目的:

    -
      -
    1. 忽略项目中不需要进行版本追踪的文件
    2. -
    3. 隐藏忽略文件
    4. -
    -
  • -
  • -

    选择maven项目模板忽略文件
    -idea-select-maven

    -
  • -
  • -

    修改忽略文件及隐藏忽略文件
    -idea-ignore-settings

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # IntelliJ project files
    .DS_Store
    .idea/
    *.iml
    out
    gen

    # eclipse
    *.classpath
    *.project
    *.springBeans
    -
    -

    关于ignore文件的写法,可以参考.gitignore 基础知识

    -
    -
  • -
-

Module Project

-

在module中有两类,一类是maven项目,还有一类是需要启动的springboot项目

-

maven module project

-
    -
  • 创建maven module
    -idea-module-maven
  • -
  • 设置maven module artifactId等信息
    -idea-module-maven-artifact
  • -
  • 设置maven module 名称及存储位置
    -idea-module-maven-name
  • -
-

springboot module project

-
    -
  • 创建springboot module
    -idea-new-module-springboot
  • -
  • 设置springboot module 信息
    -idea-module-metadata
  • -
  • 选择核心组件
    -idea-module-springboot-core
  • -
  • 设置springboot module 名称及存储位置
    -idea-module-springboot-name
  • -
-

Modify Config

-

Modify parent pom

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/05/01/zxing1/index.html b/2019/05/01/zxing1/index.html deleted file mode 100644 index df3103423..000000000 --- a/2019/05/01/zxing1/index.html +++ /dev/null @@ -1,606 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Zxing(一)二维码基础知识 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Zxing(一)二维码基础知识 -

- - -
- - - - -

移动端开发,一个避不开的老生常谈功能开发,二维码扫描识别(主要)及二维码生成(辅助),虽然已有现成的开源项目提供了功能,仅仅作为功能的开发集成和调试,其实远远不够,应该在完成功能开发的基础上去学习背后的技术点和原理,让我们更加完整的掌握该技术。废话不多说,本篇是 Zxing 相关技术的第一篇文章,本篇不会涉及到应用相关,仅仅是二维码基础知识的学习记录。

- -

QRcode

-

QRcode(全称:Quick Response Code,快速响应矩阵图码)

-
    -
  • 1994 年由日本 DENSO WAVE公司发明,
  • -
  • QR 码使用四种标准化编码模式(数字,字母数字,字节(二进制)和汉字)来存储数据
  • -
  • QR 码可以存储更多信息,可在小空间内打印,可以从 360°任一方向读取,可以对变脏和破损的图码有一定的容错能力,并且可以有效处理各种数据,支持数据合并等
  • -
  • QR 码的种类:QR 码(模型1,模型2)Micro QR 码iQR 码SQRCFrame QR
  • -
-
-

QR 码(模型1,模型2):
-模型1:最早制作的 QR 码。最高版本为 14(73x73 码元),最多可以处理 1167 位数字
-模型2:是模型1的改良版,最高版本为 40(177x177 码元),最多可以处理 7089 位数字,现在我们通常所说的 QR 码一般指模型2
-Micro QR 码:该码只有 1 个定位图案,可以在更小的空间内打印,最高版本为 M4(17x17 码元),最多可以因 35 位数字
-iQR码:可生成正方形或长方形,可以支持内外翻转,黑白反色,圆点图案(直接打标在部件上)。理论上的最高版本为 61(422x422 码元),最多大约可以处理 4万位数字
-SQRC:安全快速响应代码(Secure Quick Response code,简写SQRC)是一种QR代码,在终结符之后包含“私有数据”段而不是指定的填充字节“ec 11”。必须使用加密密钥对此专用数据段进行解密。这可用于存储私人信息和管理公司的内部信息。
-Frame QR:FrameQR是具有“画布区域”的QR码,可以灵活使用。在这个代码的中心是画布区域,其中可以灵活地安排图形,字母等,使得可以布置代码而不会丢失插图,照片等的设计

-
-

标准及发展

-
    -
  • 1997年10月:AIM(自动识别和流动协会)国际
  • -
  • 1999年1月:JIS X 0510
  • -
  • 2000年6月 -
      -
    • ISO / IEC 18004:2000信息技术 - 自动识别和数据捕获技术 - 条形码符号 - QR码(现已撤销)
    • -
    • 定义QR码模型1和2符号
    • -
    -
  • -
  • 2006年9月1日: -
      -
    • ISO / IEC 18004:2006信息技术 - 自动识别和数据捕获技术 - QR码2005条形码符号规范(现已撤销)
    • -
    • 定义QR码2005符号,QR码模型2的扩展。不指定如何读取QR码模型1符号,或要求符合性。
    • -
    -
  • -
  • 2015年2月1日: -
      -
    • ISO / IEC 18004:2015信息 - 自动识别和数据捕获技术 - QR码条形码符号规范
    • -
    • 将QR Code 2005符号重命名为QR Code,并对某些程序和次要更正添加说明
    • -
    -
  • -
-

结构

-

qrcode-structure

-
-

图片来自维基百科,Version7

-
-

如图所示,QR码由 5 部分组成

-
    -
  1. 版本信息:记录具体的版本信息(仅存在 Version7 以上) -
      -
    • version1 是 21x21 的矩阵
    • -
    • 最高 version40 是 177x177 的矩阵
    • -
    • 计算公式:(V-1)*4+21,V 代表版本号
    • -
    -
  2. -
  3. 格式信息:记录使用的掩码纠错等级
  4. -
  5. 数据及容错密钥
  6. -
  7. 数据需求模块
  8. -
  9. 静态区域
  10. -
-

IEC 18004

-

qr-iec-18004
-IEC 18004标准中给出了详细的说明

-
    -
  • 位置探测图形、位置探测图形分隔符、定位图形:用于对二维码的定位,对每个QR码来说,位置都是固定存在的,只是大小规格会有所差异;
  • -
  • 校正图形:规格确定,校正图形的数量和位置也就确定了;
  • -
  • 格式信息:表示改二维码的纠错级别,分为L、M、Q、H;
  • -
  • 版本信息:即二维码的规格,QR码符号共有40种规格的矩阵(一般为黑白色),从21x21(版本1),到177x177(版本40),每一版本符号比前一版本 每边增加4个模块。
  • -
  • 数据和纠错码字:实际保存的二维码信息,和纠错码字(用于修正二维码损坏带来的错误)
  • -
-

掩码

-

掩码的作用

-
    -
  1. 为了对数据区域进行掩模以利于扫描仪识别,可以避免数据区域出现连续的空白或连续的黑色区,
  2. -
  3. 避免了数据区出现类似定位点样式的正方形出现。掩模图案在整个数据区域的网格内不断重复进行掩模计算(功能图形不进行掩模),数据区上对应掩模黑色模块的单元将会反转。
  4. -
  5. 每个二维码上会有两组相同的格式信息出现,并且带有 BCH 纠错
  6. -
-
-

在计算机科学中,掩码就是一个二进制串,通过和数据进行异或运算来变换数据。在 QR 码中,掩码也是通过异或运算来变换数据矩阵,所以 QR 码掩码就是预先定义好的矩阵

-
-

纠错等级

-

相对而言,容错率愈高,QR 码图形面积愈大,所以一般折中使用 15% 容错能力

-

| 错误修正容量 |
-| — | — |
-| L 等级 | 7%的字码可被修正 |
-| M 等级 | 15%的字码可被修正 |
-| Q 等级 | 25%的字码可被修正 |
-| H 等级 | 30%的字码可被修正 |

-

编码 QR 码步骤

-
    -
  1. 数据分析(data analysis):分析输入数据,根据数据决定要使用的 QR 码版本、容错等级和编码模式
  2. -
  3. 编码数据(data encoding):根据选择的编码模式,将输入的字符串转变成比特流,插入模式标识码(mode indicator)和终止标识符(terminator),将比特流切分成 8 比特的字节,加入填充字节来满足标准的数据字码数要求
  4. -
  5. 计算容错码(error correction coding):对步骤二产生的比特流计算容错码,附在比特流之后。高版本的编码方式可能需要将数据流切分成块再分别进行容错码计算
  6. -
  7. 组织数据(structure final message):根据结构图把步骤三得到的有容错的数据切分,准备填充
  8. -
  9. 填充(module placement in matrix):把数据和功能性图样根据标准填充到矩阵中
  10. -
  11. 应用数据掩码(data masking):应用标准中的 8 个数据掩码来变换编码区域的数据,选择最优的掩码应用
  12. -
  13. 填充格式和版本信息(format and version information):计算格式和版本信息填入矩阵,完成 QR 码
  14. -
-

附录

-
    -
  • 维基百科·QR码·中文
  • -
  • 维基百科·QR码·英文·推荐
  • -
  • 二维码(QR code)基本结构及生成原理
  • -
  • QRcode
  • -
  • 二维码的生成细节和原理
  • -
  • 为程序员写的Reed-Solomon码解释
  • -
  • ISO/IEC 18004:2015·PDF
  • -
  • ISO/IEC 18004:2015·PDF
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/05/02/zxing2/index.html b/2019/05/02/zxing2/index.html deleted file mode 100644 index 48441d1f7..000000000 --- a/2019/05/02/zxing2/index.html +++ /dev/null @@ -1,553 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Zxing(二)Android 模块应用源码探索 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Zxing(二)Android 模块应用源码探索 -

- - -
- - - - -

ZXing(“Zebra Crossing”)用于Java,Android的条形码扫描库。虽然当前开源库仅处于维护模式,意味着更改是由贡献的补丁来驱动,只会考虑错误修复和次要的增强功能

-

本篇开启 ZXing项目Android 模块的探索学习之路,那么首先我们要集成该模块到项目中

- -

模块集成

-
    -
  • 下载官方项目Zxing
  • -
  • 使用 AS 创建一个新的 Project
  • -
-
-

编译环境

-
-
    -
  • Android studio:3.4
  • -
  • gradle:5.1.1
  • -
  • SDK:28
  • -
  • JDK:1.8
  • -
-

演示项目rc-android-zxing

-

导入步骤

-
    -
  • 导入 module
    -import_module
  • -
  • 选择 module
    -select_import_module
  • -
  • 移除最小及目标版本设置
    -remove_min_target_version
  • -
  • 添加项目核心依赖
    -import_dependencies
    -com.google.zxing:android-core:3.3.0:实质是android-core模块
    -com.google.zxing:core:3.3.3:实质是core模块
  • -
  • 删除appmodule(可选)
  • -
-

项目展示

-

zxing

-

当然你也可以下载官方提供的应用google play

-

异常问题处理

-

相机出现问题

-
表现
-

如果你运行的设备是 Android 6.0 以上版本,那么在启动应用程序后,应该会提示你“很遗憾,Android 相机出现问题,你可能需要重启设备”,如下图
-project_problem

-
分析
-

分析运行日志,进行定位CaptureActivity.java类,在初始化相机时,由于没有相机权限,因此无法正常运行应用
-zxing_error_log

-
解决方式
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// CaptureActivity.jaca
// line 266 && line 443
mHolder = surfaceHolder;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (ContextCompat.checkSelfPermission(CaptureActivity.this,
android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// 先判断有没有权限 ,没有就在这里进行权限的申请
ActivityCompat.requestPermissions(CaptureActivity.this,
new String[]{Manifest.permission.CAMERA}, CAMERA_OK);
} else {
// 说明已经获取到摄像头权限了
initCamera(surfaceHolder);
}
} else {
initCamera(surfaceHolder);
}

// line 803
@Override
public void onRequestPermissionsResult(int requestCode
, @NonNull String[] permissions, @NonNull int[] grantResults) {
// If request is cancelled, the result arrays are empty.
if (requestCode == CAMERA_OK) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
initCamera(mHolder);
}
}
}
-

其他问题

-

同样我们在EncodeActivity.java文件中加入读取内存卡的权限READ_EXTERNAL_STORAGE

-

项目分析

-

项目概要

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
rc-android-zxing
├── book/
├── camera/
├── clipboard/
├── encode/
├── history/
├── result/
├── share/
├── wifi/
├── AmbientLightManager
├── BeepManager
├── CaptureActivity
├── CaptureActivityHandler
├── Contents
├── DecodeFormatManager
├── DecodeHandler
├── DecodeHintManager
├── DecodeThread
├── FinishListener
├── HelpHelper
├── InactivityTimer
├── Intents
├── IntentSource
├── LocaleManager
├── PreferencesActivity
├── PreferencesFragment
├── ScanFromWebPageManager
├── ViewfinderResultPointCallback
└── ViewfinderView

-

项目源码

-

总结

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/05/26/android-location1/index.html b/2019/05/26/android-location1/index.html deleted file mode 100644 index 663d9c764..000000000 --- a/2019/05/26/android-location1/index.html +++ /dev/null @@ -1,509 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Android 定位知多少(一) | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Android 定位知多少(一) -

- - -
- - - - -

手机行业持续不断发展,为我们生活带了很多便利,在我们生活中到处都存在它的痕迹,它不仅是一个工具而且还是有温度的组手,协助你解决生活中的各种问题,渐渐成为了人们不可或缺的“器官”。它为什么就能进化成人类的一部分呢?其中一个重要的功能就是定位,看似单一的功能却渗透了我们各种场景,比如:定位,导航,这种基础的功能,还基于定位社交聊天,运动轨迹画像,出行等等,解决了人与人,人与物,物与物之间在位置上的问题。那么我本节就来聊一聊定位相关的一些知识,以及手机是如何在 Android 系统中是如何进行定位的

- -

在 Android 系统中,定位主要分为两类:

-
    -
  • 硬件定位(GPS 定位,北斗定位 ……)
  • -
  • 网络定位 -
      -
    • 基站定位
    • -
    • WiFi 定位
    • -
    -
  • -
-

参考

-
    -
  1. 用户位置
  2. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/05/26/android-location2/index.html b/2019/05/26/android-location2/index.html deleted file mode 100644 index ec33c71f7..000000000 --- a/2019/05/26/android-location2/index.html +++ /dev/null @@ -1,493 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Android 定位知多少(二) | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Android 定位知多少(二) -

- - -
- - - - -

本篇主要讲解定位策略

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/05/27/ooad-uml/index.html b/2019/05/27/ooad-uml/index.html deleted file mode 100644 index 2b9e9d915..000000000 --- a/2019/05/27/ooad-uml/index.html +++ /dev/null @@ -1,745 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -OOAD 与 UML | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- OOAD 与 UML -

- - -
- - - - -

OOAD(Object Oriented Analysis and Desigin) 是根据 OO 的方法学,对软件系统进行分析和设计的过程

-
    -
  • OOA(Object Oriented Analysis):分析阶段
  • -
  • OOD(Object Oriented Desigin):设计阶段
  • -
-

What to do

-

分析阶段主要解决以下问题

-
    -
  • 建立针对业务问题域的清晰视图
  • -
  • 列出系统必须要完成的核心任务
  • -
  • 针对问题域建立公共词汇表
  • -
  • 列出针对此问题域的最佳解决方案
  • -
- -

How to do

-

设计阶段主要解决以下问题(How to do?)

-
    -
  • 如何解决具体的业务问题
  • -
  • 引入系统工作所需的支持元素
  • -
  • 定义系统的实现策略
  • -
-

OOP的主要特征

-

抽象(abstract)

-
    -
  • 忽略到一个对象或实体的细节而只关注其本质特征的过程
  • -
  • 简化功能与格式
  • -
  • 帮助用户与对象交互
  • -
-

封装(encapsulation)

-
    -
  • 隐藏数据和实现
  • -
  • 提供公共方法供用户调用功能
  • -
  • 对象的两种视图 -
      -
    • 外部视图:对象能做的工作
    • -
    • 内部视图:对象如何完成工作
    • -
    -
  • -
-

继承(inheritance)

-
    -
  • 通过存在的类型定义新类型的机制
  • -
  • 通常在两个类型之间存在“is a” 或 “kind of” 这样的关系
  • -
  • 通过继承可实现代码重用,另外继承也是多态的基础
  • -
  • 如苹果 “is a” 水果
  • -
-

多态(polymorphism)

-
    -
  • 一个名称,多种形式
  • -
  • 基于继承的多态
  • -
  • 调用方法时根据所给对象的不同选择不同的处理方式
  • -
  • 如 Football——play()
  • -
  • 给出一个具体的足球或篮球,用户自动知道该使用谁的方式去执行
  • -
-

关联(association)

-
    -
  • 对象之间交互时的一种引用方式
  • -
  • 当一个对象通过对另一个对象的引用去使用另一个对象的服务或操作时,两个对象之间便产生了关联
  • -
  • 如 person 使用 computer,person 与 computer 之间就存在了关联关系
  • -
-

聚合(aggregation)

-
    -
  • 关联关系的一种,一个对象成为另一个对象的组成部分
  • -
  • 使用关系强的关联
  • -
  • 在两个对象之间存在 “has a”这样的关系,一个对象作为另一个对象的属性存在,在外部对象被产生时,可由客户端指定与其关联的内部对象
  • -
-
-

如汽车与轮胎,轮胎作为汽车的一个组成部分,它和汽车可由分别产生以后转配起来使用,但汽车可由换新轮胎,轮胎也可以卸下来给其他汽车使用

-
-

组合(composition)

-
    -
  • 当一个对象包含另一个对象时,外部对象负责管理内部对象的生命周期的情况
  • -
  • 关联关系中最为强烈的一种
  • -
  • 内部对象的创建由外部对象自己控制
  • -
  • 外部对象不存在时,内部对象也不能存在
  • -
-
-

如电视机与显示器

-
-

内聚与耦合(cohesion & coupling)

-
    -
  • 域模型是面向对象的。在面向对象术语中域模型也可称为设计模型。
  • -
  • 域模型由以下内容组成 -
      -
    • 关联(Association):一对多,多对一,一对一
    • -
    • 依赖(Dependency)
    • -
    • 聚集(Aggregation):整体和部分之间的关系
    • -
    • 一般化(泛化)(Generalization):类与类之间的继承
    • -
    -
  • -
  • 内聚:度量一个类独立完成某项工作的能力
  • -
  • 耦合:度量系统内或系统之间依赖关系的复杂度
  • -
  • 设计原则:增加内聚,减少耦合
  • -
-

开发过程概述

-

传统开发过程

-
    -
  • 瀑布模型(真实环境,不可能满足这些)
  • -
-

统一软件开发过程(USDP)

-

特点: 项目是迭代,递增

-
    -
  • 迭代指生命周期中的一个步骤
  • -
  • 迭代导致“递增”或者是整个项目的增长
  • -
  • 大项目分解为子项目
  • -
  • 在每一个迭代的阶段,应该做以下工作 -
      -
    • 选择并分析相关用例
    • -
    • 更加所选架构进行设计
    • -
    • 在组件层次实现设计
    • -
    • 验证组件满足用例的需要
    • -
    -
  • -
  • 当一次迭代满足目标后,开发进入下一个迭代周期
  • -
  • 每一个周期包含一次或多次迭代
  • -
  • 一个阶段的结束称之为“里程碑”
  • -
-

阶段

-

初始化阶段

-

该阶段的增量集中于:

-
    -
  • 项目启动
  • -
  • 建立业务模型
  • -
  • 定义业务问题域
  • -
  • 找出主要的风险因素
  • -
  • 定义项目需求的外延
  • -
  • 创建业务问题域的相关说明文档
  • -
-

细化阶段

-

本阶段的增量集中于

-
    -
  • 高层的分析与设计
  • -
  • 建立项目的基础框架
  • -
  • 监督主要的风险因素
  • -
  • 制订达成项目目标的创建计划
  • -
-

构建阶段

-

本阶段的增量集中于

-
    -
  • 代码及功能的实现
  • -
-

移交阶段

-

本阶段的增量集中于:

-
    -
  • 向用户发布产品
  • -
  • beta 测试
  • -
  • 执行性能调优,用户培训和接收测试
  • -
-

阶段特点

-

每一个阶段所包含的工作流,每一次递增都由 5 个部分工作流组成

-
    -
  • 需求与初始化分析
  • -
  • 分析
  • -
  • 设计
  • -
  • 实现
  • -
  • 测试
  • -
  • 每一次迭代执行工作流的深度不同
  • -
  • 早期的迭代在深度上覆盖初始工作流,后期迭代在深度上覆盖后期工作流
  • -
  • 80/20原则
  • -
-

UML

-

UML(Unified Modeling Language)统一建模语言,图形化语言表示,它可以帮助我们在 OOAD 过程中标识元素,构建模块,分析过程并可通过文档说明系统中的重要细节

-

静态模型(static model)

-
    -
  • 创建并记录一个系统的静态特征
  • -
  • 反映一个软件系统基础,固定的框架结构
  • -
  • 创建相关问题域主要元素的视图
  • -
  • 静态建模包括: -
      -
    • 用例图(use case diagrams)
    • -
    • 类图(class diagrams)
    • -
    • 对象图(object diagrams)
    • -
    • 组件图(component diagrams)
    • -
    • 部署图(deployment diagrams)
    • -
    -
  • -
-

动态模型(dynamic model)

-
    -
  • 用以展示系统的行为
  • -
  • 动态建模包括: -
      -
    • 时序图(sequence diagrams)
    • -
    • 协作图(collaboration diagrams)
    • -
    • 状态图(state chart diagrams)
    • -
    • 活动图(activity diagrams)
    • -
    -
  • -
-

UML 其他重要元素

-
    -
  • 包(package)
  • -
  • UML 的扩展机制 -
      -
    • 注释(comments)
    • -
    • 构造型(stereotypes)
    • -
    • 标记值(tagged values)
    • -
    • 限制(constraints)
    • -
    -
  • -
-

示例

-

用例图

-
    -
  • 展示系统的核心功能及逾期交互的用户
  • -
  • 用户被称为“活动者”(Actor)
  • -
  • 用例使用椭圆表示
  • -
  • 为简化建模过程,用例图可以标注优先级
    -uml-usecase-diagram
  • -
-

类图

-
    -
  • 表现类的特征
  • -
  • 类图描述了多个类,接口的特征,以及对象之间的协作与交互
  • -
  • 由一个或者多个矩形区域构成,内容包括 -
      -
    • 类型(类名)
    • -
    • 属性(可选)
    • -
    • 操作(可选)
      -uml-class-diagram
    • -
    -
  • -
-

对象图

-
    -
  • 表现对象的特征
  • -
  • 对象图展现了多个对象的特征及对象之间的交互
    -uml-object-diagram
  • -
-

组件图

-
    -
  • 表示软件组件之间的关系
    -uml-component-diagram
  • -
-

部署图

-
    -
  • 表现用于部署软件应用的物理设备信息
    -uml-deployment-diagram
  • -
-

时序图

-
    -
  • 捕捉一段时间范围内多个对象之间的交互信息
  • -
  • 强调消息交互的时间顺序
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/06/01/microservices/index.html b/2019/06/01/microservices/index.html deleted file mode 100644 index 0f905970d..000000000 --- a/2019/06/01/microservices/index.html +++ /dev/null @@ -1,821 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -【译】• 微服务 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 【译】• 微服务 -

- - -
- - - - -

这是第一篇翻译文章,用于学习近些年火热的微服务,这篇是微服务概念是由 James Lewis 所著,虽然官网已有中文翻译,但是在学习过程中,应该应该动手输出,这样有助于对知识的理解和记忆,废话不多说,开始翻译

-

微服务

-
-

近些年术语“微服务架构”就像雨后春笋般蓬勃的发展,微服务描述软件应用设计是独立可部署服务一个特殊方式。虽然这些都不够准确的去定义一个架构风格,但存在一些通用的特质(大家达成共识的特征),如何去组织围绕业务能力,如何自动化部署,端点的智能发现,以及语言和数据去中心化的控制

-
- -

James Lewis

-

James Lewis 是 ThoughtWorks 的首席顾问,也是技术顾问委员会的成员。James 利用小型协作服务构建应用程序的兴趣起源于大规模集成企业系统的背景。他构建数量级的系统都使用微服务,并且几年来,他一直积极参与不断地社区发展

-

Martin Fowler

-

Martin Fowler 是一个作者,演讲家,和普通的软件开发,他一直对如何组件化软件系统的问题感到困惑,他希望微服务能够实现其倡导者所发现的早期承诺

-
-

“微服务”在当时任然是一个新的名词。虽然我们的自然倾向是通过这些构建,这个技术分隔软件系统,这个术语描述了一种我们发现越来越有吸引力的软件系统风格。我们已经看到很多项目在过去的几年中使用这种风格,到目前为止的结果是积极的,以至于对于我们的许多同事而言,这已成为构建企业级应用的默认样式。然而,遗憾的是,没有太多信息可以描述微服务的风格以及微服务是如何实现

-

简而言之,微服务架构风格[1]是一个开发单应用作为小型服务套件开发模式,每个应用运行在自己的进程中并且他们之间通过轻量级的机制进行通信,通常的如 HTTP 资源 API。这些服务围绕业务能力并且这些都是可以独立的自动化部署。这些服务我们进行去中心化的集中管理。这些服务可以使用不同的编程语言来编写,同样也可以使用不同的数据存储技术

-

开始解释微服务风格,将它与单体风格进行比较是有用的:作为单元构建的单片应用程序。企业应用程序通常由 3 个之上主要构成部分:

-
    -
  • 客户端(由用户机器上的浏览器中运行的 HTML 页面和 JavaScript 组成)
  • -
  • 数据库(由插入到公共中的许多表组成,通常是关系型,数据库管理系统)
  • -
  • 服务端应用程序。
    -这个服务端应用程序处理 HTTP 请求,执行域逻辑,从数据库中检索和更新数据,并选择装配发送到浏览器的 HTML 视图。这个服务端应用程序是一个单体的,一个逻辑可执行文件[2] ,任何一次更改都生成一个新的版本去构建和部署
  • -
-

这种单体服务是构建这种系统的自然方式。所有的请求逻辑处理都运行在一个进程中,允许你使用语言的基本特性将应用划分为类,功能和命名空间。对于一些其他样例,你可以运行和测试应用在开发者的笔记本上,并使用部署管道确保已正确测试并部署到生成环境中。你可以通过负载均衡运行许多实例来进行水平扩展(常见单体应用模式 前面通过Nginx负载均衡,在 Nginx 后面运行多个应用实例)

-

单体应用程序可以成功,但是越来越多的人感到沮丧-特别是随着更多应用程序部署到云端。更改周期紧密相连-对应用程序的一小部分更改,需要重新构建和部署整个应用。随着时间的推移,通常很难保持良好的模块化结构,是的更难以保持改变只影响模块中的一个模块的更改。扩展需要扩展整个应用程序,而不是需要更多资源的部分扩展

-

图 1:单体应用和微服务
-从上图可知单体应用和微服务在部署的角度(可升缩角度)来讲:

-
    -
  • 单体应用:进行可升缩,是将单体应用整个进行升缩,每台机器上的应用都是相同的
  • -
  • 微服务:每个服务都是独立的单元,可根据需要对服务单元进行任意组合进行升缩,每台机器上的应用是不相同的
  • -
-

这些挫折导致了微服务架构的风格:构建应用程序作为服务套件。事实上服务是独立部署和可扩展的,每个服务之间也提供坚实模块的边界,甚至允许不同的编程语言编写不同的服务。它们也可以由不同的团队来管理

-

我们并不认为微服务风格是新颖的或创新的,其根源可以归结为 Unix 的设计原则。但是我们认为这些没有足够的人考虑微服务架构风格,如果使用它们,许多软件的开发会更好,从中获益匪浅

-

微服务架构的特征

-

我们不能说微服务架构风格有正式的定义,但我们可以尝试描述我们认为合适标签的架构的共同特性。与概述共同特征的任何定义一样,并非所有的微服务架构都具备所有的特征,但我们确实希望大多数微服务架构都具有大多数的特性。虽然我们的作者一直是这个相当宽松社区的积极成员,我们的目的是尝试描述我们在自己的工作中所看到的以及我们所知道的团队的努力,特别是我们没有规定一些符合的定义

-

服务组件化

-

只要我们参与软件行业,人们一直希望通过将组件集成在一起来构建系统,我们在物质世界中看待事物的方式有很多类似,在过去的几十年中,我们已经看到了大多数语言平台的大型公共 libraries 的大量进展

-

在谈论组件时,我们遇到了组件构成的困难定义,我们的定义组件是一个可独立更换和升级的软件单元

-

微服务架构会使用到这些 libraries,但他们讲自己的软件组件化的主要方式是分解为服务。我们定义 libraries 作为组件链接到程序中,也可以使用内存函数中调用的组件,而服务是进程外的组件,它们与诸如 Web 服务请求或远程调用之类的机制进行通信。(这与许多 OOP[3] 中的服务对象感念不同)

-
-

所谓的库都是调用在同一个进程当中,而服务的调用是跨进程的,要通过 Web 请求的方式或者是 RPC 的方式进行通信

-
-

将服务用作组件(而不是 libraries)的一个主要原因是服务可以独立部署。如果你在单个进程中有多个 libraries组成的应用程序[4],则对任何单个组件的更改都会导致必须重新部署整个应用程序。但如果一个应用由多个服务组成,你可以期望任何单服务的改变仅需要更新自己。这不是绝对的,一些更改改变了部分服务接口,从而导致一定的协调,但良好的微服务架构的目标是通过服务合同中的内聚服务边界和演化机制来最小化这些架构

-

将服务用作组件的另一个结果是更明确的组件接口,多数语言没有很好的机制来定义已发布的接口。通常这并不仅仅只有文档和原则性问题,来防止客户破坏组件的封装原则,而且会导致组件间过度紧密耦合。通过使用显示远程调用机制,服务可以更容易地避免这种情况

-

使用这种服务也有一些缺点。远程调用通常要比进程内调用成本要高,因此远程调用需要更粗粒度的,这通常更难以使用。如果你需要去更改组件间的职责分配,那么当你跨越流程边界时,这种行为的变化就更难

-

在第一次中,我们可以观察到服务可以映射到运行时的进程,但这只是一个大致的描述。一个服务可能包含多个进程,这些进程始终一起开发和部署,这样的应用进程和数据库是这个服务所独有的

-

围绕业务能力进行组织

-

在寻找将大型应用程序拆分为多个部分时,通常管理侧重于技术层,导致 UI 团队,服务器逻辑团队和数据库团队。当团队按照这些方式分开时,即使是简单的更改也可能导致跨团队项目需要时间和预算批准。一个聪明的团队围绕这个进行优化,并未减少这两个情况的发生——会强制将逻辑放置到可以访问的应用中。换句话说,逻辑无处不在。这就是康威定律[5] 的一个例子

-
-

Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure
-——Melvyn Conway, 1967

-
-

Conway's Law in action
-微服务划分方法是不同的,分为围绕业务能力组织的服务。此类服务为该业务领域采用广泛的软件实现,包括用户页面,持久存储,以及任何额外协作。因此,团队是跨职能的,包括开发所需的全部技能:用户体验,数据库和项目管理

-

Service boundaries reinforced by team boundaries

-
-

微服务有多大?
-虽然“微服务”已经成为这种架构风格的流行名称,但它的名字确实导致了对微服务的不关注以及关于什么构成“micro”的争论。在我们与微服务从业者的对话中,我们看到了一系列服务规模。报道的最大数量遵循亚马逊的 Two Pizza Team 的概念(比如:整个团队都可以讨厌两个披萨),意味着不超过十二人。对于规模较小的服务,我们已经看到一个6人的团队在支持6个服务。

-

这导致了这样的问题:在这个尺寸范围内是否存在足够大的差异,每个人的服务和每个服务的尺寸不应该集中在一个微服务的标签下。目前我们认为将它们组合在一起会更好,但当我们进一步探索这种风格时,我们肯定会改变主意

-
-

以这种方式组建的一家公司是 www.comparethemarket.com。跨职能团队负责构建和运营每个产品,每个产品分为多个通过消息总线进行通信的单独服务。

-

大型单机应用程序也可以围绕业务功能进行模块化,尽管这不是常见的情况。当然,我们会敦促一个庞大的团队构建一个单体的应用,以便在业务线上划分自己。我们在这里看到的主要问题是,它们往往围绕太多的背景进行组织。如果整体跨许多这些模块化边界,那么团队中的个体成员很难将它们适应其短期组织中。此外,我们看到模块化生产线需要大量的规范来执行。服务组件所需要的更明确的分离是的更容易保持团队边界清晰

-

产品不是项目

-

我们看到的大多数应用程序开发工作都使用项目模型:其目的是提供一些软件然后被认为是完成的。完成后,软件将移交给维护组织,构建他的项目团队将被解散。

-

微服务支持者倾向于避免这种模式,而是倾向于认为团队应该在其整个生命周期内拥有产品。对此的一个共同启示是亚马逊的概念“你构建,运行它”,开发团队对生产中的软件负全部责任。这使的开发人员能够日常接触他们的软件在生成中的行为,并增加与用户的联系,因此他们必须承担至少一些支持工作。

-

产品心态,与业务能力的联系紧密相连。不是将软件视为一组要完成的功能,而是存在一种持续的关系,其中的问题是软件如何帮助其用户增强业务能力

-

没有理由不采用单一应用程序采用相同的方法,但较小的服务粒度可以更容易地在服务开发人员和用户之间创建个人关系

-

智能端点和哑的 pips

-

在构建不同进程间通信结构时,我们已经看到许多产品和方法都强调将重要的smarts放入沟通机制本身。一个很好的例子是企业服务总线(ESB),其中 ESB 产品通常包括用于消息路由,编排,转换和应用业务规则的复杂工具。

-
-

微服务和 SOA
-当我们谈到微服务时,一个常见的问题,这是否是我们十年前看到的面向服务的体系结构(SOA),这一点是有道理的,因为微服务风格非常类似于 SOA 的一些拥护者所支持的。然而,问题在于 SOA 意味着太多不同的东西,并且大多数时候我们遇到称为“SOA”的东西,它与我们在这里描述的样式有很大不同,通常是由于专注于用于集成单片应用程序的 ESB

-

特别是我们已经看到了许多拙劣的服务导向实现——从隐藏 ESB[6] 中的复杂性的趋势,失败的多年计划,耗资数百万美元,没有任何价值,积极治理模式,积极抑制变化,有时很难看到过去的这些问题

-

当然,微服务社区中使用的许多技术都是从开发人员在大型组织中集成服务的经验中发展而来的。容忍读者模式就是一个例子。使用网络努力做出了贡献,使用简单的协议是从这些经验中得到的另一种方法——远离中心标准的反应,这种标准已达到复杂性,坦率地说,令人叹为观止(只要你需要一个本体来管理你的本体,你就知道你遇到了很大的麻烦)

-

SOA 的这种场景表现导致一些服务提倡者完全拒绝 SOA 标签,尽管其他人认为服务是 SOA 的一种形式,也许正确的服务向导,无论哪种方式,SOA[7] 意味着这些不同的事物意味着有一个更清晰地定义这种建筑风格的术语是有价值的

-
-

微服务社区倾向于采用另一种方法:智能端点和愚蠢的 pips。从微服务构建的应用程序旨在尽可能地分离和聚集——他们拥有自己的域逻辑,在经典的 Unix 意义上更像是过滤器——接收请求,适当地应用逻辑并产生响应。这些是使用简单的 RESTish 协议而不是复杂的协议(如 WS-Choregoraphy 或 BPEL 中央工具的编排)编排的。

-

最常用的两个协议是 HTTP 请求——响应资源 API 和轻量级消息[8] 传递。第一个最好的表达方式是

-
-

Be of the web, not behind the wed
-– lan Robinson

-
-

微服务团队使用万维网(在很大程度上,Unix)构建的原则和协议。经常使用的资源可以通过开发人员或操作人员的非常小的努力来缓存。

-

常用的第二种方法是通过轻量级消息总线进行消息传递。选择的基础设施通常是哑的(哑仅作为消息路由器的行为)—— 向 RabbitMQ 或者 ZeroMQ 这样的简单实现不仅仅提供可靠的异步结构——智能功能存在于那些生产和消费诸多消息的各个端点中,即存在于各个服务中。

-

在一个单体应用中,组件在进程中执行,它们之间通信是通过方法调用或函数调用。将整体变为微服务的最大问题在于改变通信模式。从内存中方法调用到 RPC 的简单转换导致繁琐的通信,这种通信效果不佳。相反,您需要粗粒度的方法替换细粒度的通信。

-

去中心化的治理

-

集中治理的后果之一是在单个技术平台上实现标准化的趋势。经验表明,这种方法是有限的——不是每个平台是一样的,不是每个平台的解决方案是一致的。我们推荐使用正确的工具来完成工作,而单体应用程序在一定程度上利用不同的语言,但这并不常见

-

将单个应用组件拆分为多个服务,我们可以在构建每个组件时做出选择。你希望使用 Node.js 建立一个简单的报告页面?没问题。想通过 C++ 来实现出彩的实时组件?没毛病。想换不同风格的数据库,以更好地适应一个组件的读取行为?可以重建

-

当然,只是因为你可以做某件事,并不意味着你可以应该——但以这种方式划分你的系统意味着你可以选择

-

构建微服务的团队也更喜欢采用不同的标准方法。他们更倾向于其他开发人员可以使用的有用工具来解决与他们面临的类似问题,而不是使用在纸上某处写下的一组定义标准。这些工具通常从实现中收集并广泛的共享,有时,但不仅仅是使用内部开源模型。现在 Git 和 GitHub 已经成为事实上的版本控制系统,开源实践在内部变得越来越普遍。

-

Netflix 是遵循这一理念的组织的一个很好的例子。共享有用的,尤其是经过实战考验的代码,因为鼓励其他开发人员以类似的方式解决类似问题,但如果需要,可以选择不同的方法。共享库往往侧重于数据存储,进程间通信的常见问题,我们将在下面进一步讨论基础架构自动化

-

对于微服务社区来说,管理费用特别缺乏吸引力。这并不是说社区不重视服务契约。恰恰相反,因为往往会有更多。只是他们正在寻找管理这些契约的不同方式。像容错读取消费者驱动的契约这样的模式通常应用于微服务。这些援助服务契约独立发展。在构建过程中执行消费者驱动的契约可以增强信心,并提供有关您的服务是否正常运行的快速反馈。事实上,我们知道澳大利亚的一个团队通过消费者驱动的契约推动服务的建设。他们使用简单的工具来定义合同服务。在编写新服务的代码之前,这将成为自动构建的一部分。然后,该服务仅构建在满足合同的程度——在构建新软件时避免“YAGNI”[9] 困境的优雅方法。这些技术和围绕他们成长的工具通过减少服务间的时间耦合来限制重要合同管理的需要

-
-

多语言,多选择
-JVM 作为平台的发展只是在一个通用平台中混合语言的最新例子。近十年依赖,通常的做法是采用更高级别的语言来更高级别的抽象。同样,在平台底层以更低层次的编程语言编写性能敏感的代码也很普遍。然而,许多单块系统并不需要这种级别的性能优化,另外 DSL 和更高层次的抽象也不常用(这令我们感到失望)。相反,许多单体应用通常就使用单一编程语言,并且对所用的技术数量进行限制的趋势[10]

-
-

也许去中心化治理的最高点就是建立它/运行它,由亚马逊推广的精神。团队负责他们构建的软件的所有方面,包括全天候运行软件。这种责任水平的下放绝对不是常态,但我们确实看到越来越多的公司将责任推向开发团队。Netflix 是另一个采用这种精神[11] 的组织。每天晚上凌晨 3 点您被你的寻呼机唤醒,无疑是在编写代码时专注于质量的强大动力。这些想法与传统的集中治理模式相差甚远

-

去中心化数据管理

-

去中心化数据管理以多种不同的方式呈现。在最抽象的层面上,它意味着世界的概念模型在不同系统之间会有所不同。在整个大型企业时,这是一个常见问题,客户的销售视角将与支持视角不同。从销售视角中称为“客户”的某些内容,可能根本不会出现在支持视角中。那些在两个视角中具有相同属性的事物,或许在语义上有微妙的不同

-
-

经过实战检验的标准和强制执行的标准
-微服务团队倾向于避开企业架构小组制定的严格执行标准,但很乐意使用甚至宣传 HTTP,ATOM 和其他微格式等开放标准的使用,这有点很二分法

-

关键的区别在于如何制定标准以及如何实施标准。有 IETF 等团体管理的标准只有在更广泛的世界中有多高实施时才能成为标准,并且通常来至于成功的开源项目

-

这些标准与企业的许多标准不同,后者通常由最近没有编程或受供应商过度影响的团体开发

-
-

此问题在应用程序间很常见,但也可能在应用程序中发生,特别是将应用程序划分为单独的组件时。一种有用的思考方式是“领域驱动设计”中的“限定上下文”的概念。DDD 将复杂领域划分为多个限界上下文,并映射出他们之间的关系。此过程对单体和微服务架构两者都很有用,而且就像前面有关“业务功能”一节中所讨论的那样,在服务和各个限界上下文之间所存在的自然的联动关系,能有助于澄清和强化这种划分。

-

除了关于概念模型的分散决策之外,微服务还分散了数据存储决策。虽然单一应用程序更喜欢使用单个逻辑数据库来存储持久性数据,但企业通常更喜欢在一系列应用程序中使用单个数据库——其中许多决策是通过供应商的商业模式来实现。微服务更喜欢让每个服务管理自己的数据库,可以是同一数据库技术的不同实例,也可以是完全不同的数据库系统——这种方法称为"Polyglot Persistence"。你可以在整体中使用多语言持久性,但它在微服务中更常出现。
-

-

跨服务分散数据责任对管理更新有影响。处理更新的常用方法是在更新多个资源时使用事务来保证一致性。这种方法通常用于整体结构中。

-

使用这样的事务有助于保持一致性,但会产生显著的时间耦合,在多个服务中是有问题。众所周知,分布式事务很难实现,因此微服务架构强调服务之间的无事务协调,明确承认一致性可能只有最终的一致性,而问题则通过补偿操作来处理。

-

选择以这种方式管理不一致是许多开发团队面临的新挑战,但它通常与业务实践相匹配。企业通常会处理一定程度的不一致,以便快速响应需求,同时采取某种逆转流程来应对错误。只要修复错误的成本低于在更大的一致性下丢失业务的成本,那么权衡是值得的。

-

基建设施自动化

-

基础设施自动糊技术在过去几年中发生了巨大变化——特别是云和 AWS 的发展降低了构建,部署和运行微服务的操作复杂性。

-

许多使用微服务构建的产品或系统都是由具有丰富的持续交付(Continuous Delivery)经验的团队构建的,并且是前身的持续集成(Continuous Integration)。以这种方式构建软件的团队广泛使用基础设施自动化技术。这在下面显示的构建管道中说明

-

basic build pipeline

-

由于这不是关于持续交付的文章,我们将在这里引起注意几个关键功能。我们希望尽可能地信心使我们的软件正常工作,因此我们进行了大量的自动化测试。推广工作软件“向上”管道意味着我们自动化部署到每个新环境。

-
-

做正取的事情很容易
-我们发现由于持续交付和部署而增加自动化的一个副作用是创建有用的工具来帮助开发人员和操作人员。用于创建人工制品,管理代码库,提供简单服务或添加标准监视器和日志记录的工具现在非常普遍。网上最好的例子可能是 Netflix 的开源工具集,但还有其他一些,包括我们官方使用的 Dropwizard

-
-

一个单一的应用程序将非常愉快地构建,测试和推动通过这些环境。事实证明,一旦你投资自动化整个生产的生产之路,那么部署更多地应用程序视乎不再那么可怕。请记住,CD的目标之一就是使用部署无聊,所以无论是一个还是多个应用,只要它任然无聊就无聊无所谓[12]

-

我们看到团队使用广泛的基础设施自动化的另一个领域是管理生产中的微服务。与我们上面的断言相反,只要部署很无聊,单块和微服务之间没有太大的区别,每个部署的运营环境可能会截然不同
-Module deployment often differs

-

容错设计

-

使用服务作为组件的结果是,应用需要设计以便他们能够容忍服务的失败。由于提供者不可用(不可达)等,任何服务调用都可能失败,客户端必须尽可能优雅地对此作出响应。与单体设计相比,这是一个缺点,因为它引入了额外的复杂性来处理它。结果是微服务团队持续不断反思服务失败如何影响用户体验。Netflix 的 Simian Army 在工作日引发服务甚至数据中心的故障,以测试应用程序的弹性和监控。

-
-

断路器和“可随时上线的代码”
-断路器一词与其他一些模式一起出现发布,如 Bulkhead 和 Timeout。在构建彼此通信的应用系统时,将这些模式加以综合运用变得至关重要。Netflix 博客这篇文章很好的解释了这些模式如何应用。

-
-

这种在生产环境中所进行自动化测试,足以让大多数运维组织兴奋地浑身颤栗,就像在一周的长假即将到来前那样。这并不是说单体式架构风格不具备复杂的监控设置——在我们的经验中,这在单体系统中并不常见罢了。

-

由于服务可能随时发生故障,因此能够快速检测故障并在可能情况下自动恢复服务非常重要。微服务应用程序非常重视应用程序的实时监控,检查“架构元素指标”(数据库每秒获得多少请求)和“业务相关指标”(例如每分钟收到多少订单)。当系统某个地方出现问题,语义监控可以提供一个预警,从而触发开发团队跟进和调查工作。

-

这对于微服务架构尤为重要,因为微服务对编排和事件协作的偏好会导致紧急行为。虽然许多权威人士赞扬偶然出现的价值,但事实是,新兴行为有时可能是一件坏事。监控对于快速发现下不良紧急行为至关重要,因此可以修复。

-
-

“同步调用”有害
-每当您在服务之间进行多次同步调用时,您将遇到停机的乘法效应。简而言之,就是系统停机时间成为各个组件停机时间的产物。您面临一个选择,使您的呼叫异步或管理停机时间。在 www.guardian.co.uk网站上,他们在新平台上实施了一条简单的规则——每个用户请求一次同步调用,而在 Netflix,他们的平台 API 重新设计已经在 API 结构中建立了异步性。

-
-

monoliths 可以像微服务一样透明——事实上,他们应该是。不同之处在于您绝对需要知道在不同进程中运行的服务何时断开连接。对于同一过程中的库,这种透明性不太可能有用。

-

微服务团队希望看到针对每个服务的复杂监控和日志记录设置,例如显示上/下状态的仪表板以及各种运营和业务相关指标。有关断路器状态,当前吞吐量和延迟的详细信息是我们经常遇到的其他示例。

-

“演进式”设计

-

微服务从业者通常拥有“演进式”设计背景,而且通常将服务分解视为额外的工具,使应用程序开发人员能够控制应用程序中的更改而不会减慢变更。变更控制并不一定意味着改变——通过正确的态度和工具,你可以对软件进行频繁,快速和良好控制的变更。

-

每当你尝试将软件系统分解为组件时,就面临着如何进行划分各个部分的决定——我们决定将应用程序切分的遵循的原则是什么?组件的关键属性是独立替换和可升级性[13] 的特点——这意味着需要寻找这些点,即想象在不影响其合作者的情况下重写组件。事实上,许多微服务团队通过明确预期服务将来会废弃,而不是守着这些服务做长期的演进。

-

Guardian 网站是一个设计和构建为单体的应用程序的一个很好例子,然而它已经开始向在微服务方向进行演进了。原先的单体系统依然是网站的核心,但在添加新特性时他们愿意以构建一些微服务的方式来进行添加,而这些微服务会去调用原先那个单体系统的 API。这种方法对于本质上是临时的功能尤其方便,例如报道体育赛事的专用页面。当使用快速开发语言时,像这样的网站就能被快速整合在一起,并在时间结束后删除。我们在金融机构看到了类似的做法,针对一个市场机会,添加新服务进来,并在几个月甚至几周后丢弃。

-

这种对可替换性的强调是模块化设计一般性原则的一个特例,即通过“演进式”模式推动模块化实现。大家都愿意将那些在同时发生变化[14] 的东西,放到同一个模块中。很少变化的部分,应该放在不同的服务中,以区别那些当前正在经历大量变动的部分。如果您发现需要同时反复变更的两个服务时,那就表明他们应该合并。

-

将组件放入服务中可以为更细粒度的发布计划添加机会。对于单体应用,任何更改都需要完整构建和部署整个应用程序。但是对使用微服务,您只需要重新部署您修改的服务。这可以简化并加快发布过程。缺点是:必须考虑当一个服务发生变化时,依赖它并对其进行消费的其他服务将无法工作。传统的集成方法是尝试使用版本控制来解决这个问题,但微服务领域中,大家更喜欢使用版本控制作为最后不得已的手段。我们可以通过将服务设计为对提供者变更,尽量能够容错来避免大量版本控制

-

未来的方向是“微服务”吗?

-

我们写这篇文章的主要目的是解释微服务的主要思想和原则。通过花时间来做到这一点,我们清楚地认为微服务架构风格是一个重要的想法——值得认真考虑企业应用程序。我们最近使用这种方式构建了几个系统,并了解其他团队已经使用并支持这种方法。

-

我们了解到那些在某种程度上做为这种架构风格的实践先驱包括:亚马逊,Netflix,GuardianUK Government Digital Servicerealeastate.com.aucomparethemarket.com。2013 年的技术大会圈子充满了各种各样的,正在转向可以归类为微服务的公司——包括 Travis CI。此外,有很多组织长期以来一直在做我们称为微服务的东西,但没有使用过这个名字(通常被标记为 SOA——尽管如我们所说,SOA 有许多互相矛盾的形式[15]

-

然而,尽管有这些积极的经验,但我们并不认为我们确信微服务是软件架构的未来发展方向。虽然到目前为止我们的经验与单体应用相比是积极的,但我们意识到没有足够的时间让我们做出充分的判断。

-

-
-

我们的同事 Sam Newman 在 2014 年的大部分时间都在撰写一本书,该书描述了我们构建微服务的经验。如果想进一步了解该主题,这应该是您的下一步

-
-

通常,您的架构决策的真正后果只有在开发它几年后才会明显。我们已经由带着强烈模块化愿望的优秀团队所做的一些项目,最终却构建出一个单体架构,并在几年内不断腐化。许多人认为微服务不太可能出现这种衰退,因为服务边界是明确的,很难随意捣乱。然而,对于那些开发时间足够长的各种系统,除非我们已经见识的足够多,否则我们无法真正评估微服务架构是如何成熟的。

-

人们可能会期望微服务成熟得很好。在组件化的任何努力中,成功取决于在组件中的适用程度。很难弄清楚组件边界的确切位置。“演进式”设计承认难以对边界进行正确定位,因此它将工作的重点放到了易于对边界进行重构之上。但是当您的组件是具有远程通信的服务时。那么重构比适用进程内库要困难的多。跨服务边界移动代码很困难,任何接口更改都需要在参与者之间协调,需要添加向后兼容性,测试变得更加复杂。

-

另一个问题是,如果组件没有干净利落地组成一个系统,那么您所做的就是将复杂性从组件内部转移到组件之间的连接。这样做的后果,不仅仅是移动复杂性,而是将其移动到一个不那么明确且难以控制的地方。当你在一个小而简单的组件内部查看时,人们很容易认为事情已经变得更好了,然而却忽略了服务之间的杂乱连接

-

最后,还有团队技能的因素。新技术往往被技术更加过硬的团队所采用。对于技术更加过硬的团队更更有效的一项技术,并不一定适用于技术略逊一筹的团队。我们已经看到很多不太熟练的团队构建混乱的单体架构,当微服务发生这种混乱时,会出现什么情况?这需要花时间来观察。一个糟糕的团队,总是会创建一个糟糕的系统——很难说微服务是减少了杂乱,还是让事情变得更糟。

-

我们听到一个合理的说法,不应该一上来就以微服务架构作为起点。相反,从单体应用开始,保持模块化。当单体系统出现问题时将其拆分为微服务。(虽然这个建议并不理想,但是好的进程内接口通常不是一个好的服务接口)

-

因此,我们谨慎乐观地写下这一点。到目前为止,我们已经看到了足够多的微服务风格,觉得它是一条值得走的路。我们无法确定最终会在哪里结束,但软件开发的挑战之一是您只能根据您当前必须提供的不完善信息作出决策。

-

参考

-

虽然这不是一个详尽的列表,但是它们是微服务从业者可以从中吸取灵感来源,或者是那些倡导的理念与本所述内容详实的一些资料

-

博客和在线文章

-
    -
  • Clemens Vasters’ blog on cloud at microsoft
  • -
  • David Morgantini’s introduction to the topic on his blog
  • -
  • 12 factor apps from Heroku
  • -
  • UK Government Digital Service design principles
  • -
  • Jimmy Nilsson’s blog and article on infoq about Cloud Chunk Computing
  • -
  • Alistair Cockburn on Hexagonal architectures
  • -
-

书籍

-
    -
  • Release it
  • -
  • Rest in practice
  • -
  • Web API Design (free ebook). Brian Mulloy, Apigee.
  • -
  • Enterprise Integration Patterns
  • -
  • Art of unix programming
  • -
  • Growing Object Oriented Software, Guided by Tests
  • -
  • The Modern Firm: Organizational Design for Performance and Growth
  • -
  • Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation
  • -
  • Domain-Driven Design: Tackling Complexity in the Heart of Software
  • -
-

简报

-
    -
  • Architecture without Architects. Erik Doernenburg
  • -
  • Does my bus look big in this?. Jim Webber and Martin Fowler, QCon 2008
  • -
  • Guerilla SOA. Jim Webber, 2006
  • -
  • Patterns of Effective Delivery.Daniel Terhorst-North, 2011.
  • -
  • Adrian Cockcroft’s slideshare channel.
  • -
  • Hydras and Hypermedia. Ian Robinson, JavaZone 2010
  • -
  • Justice will take a million intricate moves Leonard Richardson, Qcon 2008.
  • -
  • Java, the UNIX way. James Lewis, JavaZone 2012
  • -
  • Micro services architecture. Fred George, YOW! 2012
  • -
  • Democratising attention data at guardian.co.uk. Graham Tackley, GOTO Aarhus 2013
  • -
  • Functional Reactive Programming with RxJava. Ben Christensen, GOTO Aarhus 2013 (registration required).
  • -
  • Breaking the Monolith. Stefan Tilkov, May 2012.
  • -
-

论文

-
    -
  • L. Lamport,“The Implementation of Reliable Distributed Multiprocess Systems”, 1978
  • -
  • L. Lamport, R. Shostak, M. Pease,“The Byzantine Generals Problem”, 1982
  • -
  • R.T. Fielding, “Architectural Styles and the Design of Network-based Software Architectures”, 2000
  • -
  • E. A. Brewer, “Towards Robust Distributed Systems”, 2000
  • -
  • E. Brewer, “CAP Twelve Years Later: How the ‘Rules’ Have Changed”, 2012
  • -
-

总结

-

单体应用与微服务比较

-

单体应用

-

特性

-
    -
  • 调用方便,都是在一个进程内进行调用(针对于 Java 来说,就是运行在一个 JVM 上的应用)
  • -
  • 部署方式简单,
  • -
  • 事务处理方式可以容易处理
  • -
  • 由于都在一个进程内API 调用,不涉及网络的访问,因此出错的可能性要低很多
  • -
-

优缺点

-
    -
  • 优点 -
      -
    • 为人所熟知
    • -
    • 便于共享
    • -
    • 易于测试
    • -
    • 容易部署
    • -
    -
  • -
  • 缺点 -
      -
    • 复杂性逐渐变高
    • -
    • 技术债务逐渐上升
    • -
    • 部署速度逐渐变慢
    • -
    • 阻碍技术创新
    • -
    • 无法按需伸缩
    • -
    -
  • -
-

微服务

-

特性

-
    -
  • 每个微服务可独立运行在自己的进程里
  • -
  • 一系列独立运行的微服务共同构建起了这个系统
  • -
  • 每个服务为独立的业务开发,一个微服务一般玩某个特定的功能,比如:订单管理,用户管理等
  • -
  • 微服务之间通过一些轻量的通讯机制进行通信,比如通过 REST API 或者 RPC 的方式调用
  • -
-

优缺点

-
    -
  • 优点 -
      -
    • 易于开发和维护
    • -
    • 启动较快
    • -
    • 局部修改容易部署
    • -
    • 技术栈不受限
    • -
    • 按需伸缩
    • -
    • DevOps
    • -
    -
  • -
  • 缺点 -
      -
    • 运维复杂
    • -
    • 数据一致性问题
    • -
    • 集成测试复杂
    • -
    • 重复代码
    • -
    • 监控困难
    • -
    -
  • -
  • 挑战 -
      -
    • 运维要求较高
    • -
    • 分布式的复杂性
    • -
    • 接口调整成本高
    • -
    • 重复你劳动
    • -
    -
  • -
-

设计原则

-
    -
  • 单一职责原则
  • -
  • 服务自治原则
  • -
  • 轻量级通信原则
  • -
  • 接口明确原则
  • -
-

附录

-
    -
  • Microservices
  • -
  • 校验 • CeaserWang
  • -
-
-
-
    -
  1. The term “microservice” was discussed at a workshop of software architects near Venice in May, 2011 to describe what the participants saw as a common architectural style that many of them had been recently exploring. In May 2012, the same group decided on “microservices” as the most appropriate name. James presented some of these ideas as a case study in March 2012 at 33rd Degree in Krakow in Microservices - Java, the Unix Way as did Fred George about the same time. Adrian Cockcroft at Netflix, describing this approach as “fine grained SOA” was pioneering the style at web scale as were many of the others mentioned in this article - Joe Walnes, Daniel Terhorst-North, Evan Botcher and Graham Tackley. ↩︎

    -
  2. -
  3. The term monolith has been in use by the Unix community for some time. It appears in The Art of Unix Programming to describe systems that get too big. ↩︎

    -
  4. -
  5. Many object-oriented designers, including ourselves, use the term service object in the Domain-Driven Design sense for an object that carries out a significant process that isn’t tied to an entity. This is a different concept to how we’re using “service” in this article. Sadly the term service has both meanings and we have to live with the polyseme. ↩︎

    -
  6. -
  7. We consider an application to be a social construction that binds together a code base, group of functionality, and body of funding. ↩︎

    -
  8. -
  9. The original paper can be found on Melvyn Conway’s website here. ↩︎

    -
  10. -
  11. We can’t resist mentioning Jim Webber’s statement that ESB stands for “Egregious Spaghetti Box”. ↩︎

    -
  12. -
  13. Netflix makes the link explicit - until recently referring to their architectural style as fine-grained SOA. ↩︎

    -
  14. -
  15. At extremes of scale, organisations often move to binary protocols - protobufs for example. Systems using these still exhibit the characteristic of smart endpoints, dumb pipes - and trade off transparency for scale. Most web properties and certainly the vast majority of enterprises don’t need to make this tradeoff - transparency can be a big win. ↩︎

    -
  16. -
  17. “YAGNI” or “You Aren’t Going To Need It” is an XP principle and exhortation to not add features until you know you need them. ↩︎

    -
  18. -
  19. It’s a little disengenuous of us to claim that monoliths are single language - in order to build systems on todays web, you probably need to know JavaScript and XHTML, CSS, your server side language of choice, SQL and an ORM dialect. Hardly single language, but you know what we mean. ↩︎

    -
  20. -
  21. Adrian Cockcroft specifically mentions “developer self-service” and “Developers run what they wrote”(sic) in this excellent presentation delivered at Flowcon in November, 2013. ↩︎

    -
  22. -
  23. We are being a little disengenuous here. Obviously deploying more services, in more complex topologies is more difficult than deploying a single monolith. Fortunately, patterns reduce this complexity - investment in tooling is still a must though. ↩︎

    -
  24. -
  25. In fact, Daniel Terhorst-North refers to this style as Replaceable Component Architecture rather than microservices. Since this seems to talk to a subset of the characteristics we prefer the latter. ↩︎

    -
  26. -
  27. Kent Beck highlights this as one his design principles in Implementation Patterns. ↩︎

    -
  28. -
  29. And SOA is hardly the root of this history. I remember people saying “we’ve been doing this for years” when the SOA term appeared at the beginning of the century. One argument was that this style sees its roots as the way COBOL programs communicated via data files in the earliest days of enterprise computing. In another direction, one could argue that microservices are the same thing as the Erlang programming model, but applied to an enterprise application context. ↩︎

    -
  30. -
-
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/06/19/soa/index.html b/2019/06/19/soa/index.html deleted file mode 100644 index b5c81f834..000000000 --- a/2019/06/19/soa/index.html +++ /dev/null @@ -1,729 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -【译】• 面向服务的架构 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 【译】• 面向服务的架构 -

- - -
- - - - -

在学习过程中,我们首先需要将学习知识的基本概念搞清楚,而搞清楚概念最权威的方式是查阅 英文版 • 维基百科 ,或者是对应知识的官方文档上面查找相关的知识。这样学习才能学习到知识的精华,而不是阅读经过别人转译过的文章。因此,这篇文章仅是本人在学习 SOA 基本概念时,对维基百科知识的一个汇总翻译记录,不建议朋友把这篇文章当做你的学习资料,具体请查阅Service-oriented architecture

-

面向服务的架构(SOA)是一种软件设计风格。 SOA 服务通过应用组件,通过网络通信协议的方式向其他组件提供服务。SOA 的基本原则是独立于厂商,独立于产品以及独立于技术[1]。服务是一种功能的离散独立单元,可以远程访问并独立运行与更新,例如在线查询信用卡账单。

- -

一个服务在诸多 SOA 定义中有 4 个属性[2]

-
    -
  1. 它逻辑上代表具有指定结果的业务活动
  2. -
  3. 它是自包含的
  4. -
  5. 它对于消费者来说是黑盒(不可见)
  6. -
  7. 它可能包含其他基础服务[3]
  8. -
-

不同的服务可以联合起来构建大型的软件应用[4],SOA 遵循模块化编程思想,SOA 集成了分布式,独立维护和独立部署的软件组件,它通过技术和标准促使组件通过网络进行通信和协作,尤其是通过 IP 网络

-

概览

-

在 SOA 中,服务的使用是描述怎样通过元数据传递消息和解析消息的协议。该元数据描述了服务的功能特性和服务质量特征。SOA 目标旨在允许用户将大块的功能组合在一起来去构成一个应用,形成纯粹由现有服务构建并以临时方式组合的应用程序。一个服务会向调用者提供一个简单的接口,它抽象出作为黑盒子的底层复杂性。其他用户可以在不了解其内部实现的情况下访问这些独立服务[5]

-

定义概念

-

相关的流行语服务导向促进了面向服务间的松耦合。SOA 将功能分为不同的单元或服务,哪些开发人员可以通过网络访问,以便允许用户在应用程序的生产中组合和重用它们。这些服务及其相应的消费者通过明确定义的共享格式,传递数据或通过协调两个或更多服务之间的活动来互相通信

-

2009 年 10 月发布了关于 SOA 的宣言,其中提出了 6 个核心价值,如下所示

-
    -
  1. 商业价值比技术战略更重要
  2. -
  3. 战略目标比项目特定的利益更重要
  4. -
  5. 内在的互操作性比定制集成更重要
  6. -
  7. 共享服务比特定用途实现更重要
  8. -
  9. 灵活性比优化更重要
  10. -
  11. 演进式比追求初始化完美更重要
  12. -
-

SOA 可看作是连续统一体的一部分,其范围从旧的分布式计算概念和模块化编程[6] [7],通过 SOA,以及 mashupsSaaS云计算的当前实践(有些人认为是 SOA 的后代)[8]

-

原理

-

尽管许多行业已发布了自己的原则,但没有与 SOA 确切相关的行业标准,其中一些[9] [10] [11] [12]包括以下内容

-

标准地服务契约

-

服务遵循标准通信协议,有一组给定服务中的一个或多个服务描述文档共同定义

-

服务自治

-

服务之间的关系被最小化到它们只知道存在的等级

-

服务松耦合

-

无论网络位于何处,都可以从网络中的任何位置调用服务

-

服务长寿

-

服务应该设计为长寿,在可能的情况下,如果不需要新功能,服务应该避免强迫消费者进行更改。如果今天能调用的服务,到明天也应该能调用统一的服务

-

服务抽象化

-

服务充当黑盒,它们内在的逻辑对消费者是隐藏的

-

服务自治

-

服务是独立的,从设计时和运行时的角度控制它们封装的功能

-

服务无状态化

-

服务是无状态的,即返回请求的值或提供异常,从而最大限度的减少资源使用

-

服务粒度

-

确保服务具有足够的规模和范围的原则。服务向用户提供的功能必修三相关的

-

服务规范化

-

服务被分解或合并作为最小化冗余。在某些情况下,可能无法完成,这些是需要性能优化,访问和聚合的情况[13]

-

服务可组合性

-

服务可用于组成其他服务

-

服务发现

-

服务补充了交流元数据,通过它可以有效地发现和解释它们

-

服务可重用性

-

将逻辑分为多个服务,以促进代码的复用

-

服务封装

-

许多最初未在 SOA 下计划的服务可能会被封装或成为 SOA 的一部分

-

模式

-

每个SOA构建块都可以扮演以下三种角色中的任何一种:

-

服务提供者

-

它创建 Web 服务并将其信息提供给服务注册。每个提供者都会讨论大量的方法,以及为什么要公开哪些服务,哪些更重要:安全性或易用性,提供服务的价格等等。提供者还必须决定应该为给定的代理服务列出服务的类别[14]以及使用该服务需要那种协议

-

服务代理,服务注册或服务存储

-

其主要功能是使用任何潜在的请求者都能获取有关 Web 服务的信息。实施的人决定代理的范围。公开的代理随处可见,但私有的代理只能向有限的公开代理开放。UDDI 是一种早期的,不再主动支持的 Web 服务发现

-

服务消费者

-

它使用各种查找操作在代理注册中查找,然后绑定到服务提供者以调用其中一个 Web 服务。无论服务消费者需要哪种服务,它们都必须通过代理,将其与相应的服务绑定,然后使用它。如果服务提供多种服务,它们可以访问多种服务。

-

服务消费者-提供者关系由标准化服务契约[15]管理,其中包括业务部分,功能部分和技术部分。

-

服务组合模式有两种广泛的高级架构风格:服务编排。不受特定体系结构风格约束的较低级别的企业集成模式在 SOA 设计中任然具有相关性和合格性[16] [17] [18]

-

实现方法

-

SOA 可以通过 Web 服务[19]实现。这样做是为了使功能模块可以通过独立于平台和编程语言的标准协议访问。这些服务既可以代表新应用程序,也可以代表现有遗留系统的包装,使其具备网络功能。[20]

-

实现通用使用 Web 服务标准构建 SOA。一个例子是 SOAP,它在 2003 年从 W3C[21] 推荐 1.2 版本后获得广泛的行业认可。这些标准(也称为 Web 服务规范)还提供了更强的互操作性以及对锁定到专有供应商软件的一些保护。但是,也可以使用任何其他基于服务的技术(如 JiniCORBA 或者 REST)实现 SOA

-

架构可以独立于特定技术运行,因此可以使用多种技术实现,包括:

-
    -
  • 基于 WSDL 和 SOAPWeb 服务
  • -
  • 消息传递,例如,使用 ActiveMQ, JMS, RabbitMQ
  • -
  • RESTful HTTP,具有 Representational 状态转移(REST),构成自己的基于约束的架构风格
  • -
  • OPC-UA
  • -
  • WCF(Microsoft 的 Web 服务实现,构成 WCF 的一部分)
  • -
  • Apache Thrift
  • -
  • gRPC
  • -
  • SORCER
  • -
-

实现可以使用这些协议中的一个或多个,例如,可以使用文件系统机制来遵循符合 SOA 概念的进程间定义的接口规范来传递数据。关键是具有已定义接口的独立服务,可以调用它们以标准方式执行其任务,而无需预先知道调用应用程序的服务,并且没有应用程序具有或需要知道服务如何实际执行其任务。SOA 支持开发通过松耦合和可互操作的服务构建的应用程序

-

这些服务独立于底层平台和编程语言的正式定义(或契约,例如 WSDL)进行互操作。接口定义隐藏了实施特定语言的服务实现。因此,基于 SOA 的系统可以独立于开发技术和平台(例如Java,.NET等)运行。例如,运行在.NET平台上的 C# 和 用 JavaEE 平台上运行的 Java 编写的服务都可以公共复合应用程序(或客户端)使用。在任一平台上运行的应用程序也可以使用在另一个平台上运行的服务作为重用的 Web 服务。托管环境还可以包括 COBOL 遗留系统并将其作为软件服务提供。[22]

-

诸如 BPEL 之类的高级编程语言以及诸如 WS-CDLWS-Coordination 之类的规范通过提供一种定义和支持将细粒度服务编排成更粗粒度的业务服务的方法来扩展服务的概念,架构师可以将其合并到复合应用程序或门户中实现的工作流和业务流中。

-

面向服务的建模是一个 SOA 架构,可识别指导 SOA 从业者对其面向服务的资产进行概念化,分析,设计和构建的各种规程。面向服务的建模框架(SOMF)提供了一种建模语言和一个工作架构映射,描述了有助于成功的面向服务的建模方法的各种组件。它说明了识别服务开发方案的“做什么”方面的主要元素。该模型使从业者能够制定项目计划并确定面向服务的计划的里程碑。SOMF 还提供了一种通用的建模符号,已解决业务和 IT 组织之间的一致性问题。

-

组织利益

-

一些企业架构师认为,SOA 可以帮助企业更快,更经济地响应不断变化的市场条件。[23]这种体系结构促进了宏(服务)级别的重用,而不是微(类)级别的重用。它还可以简化现有 IT(传统)资产的互联和使用。

-

SOA的元素,由Dirk Krafzig,Karl Banke和Dirk Slama撰写

-

使用SOA,我们的想法是组织可以从整体上看待问题。企业拥有更多地整体控制权。从理论上讲,不会有大量的开发人员使用任何工具集让他们满意,但他们将编码为业务中设定标准。他们还可以开发企业级 SOA,封装面向业务的基础架构。SOA 被描述为汽车驾驶员提供效率的高速公路系统。关键在于,如果每个人都有车,但在任何地方都没有高速公路,那么事情就会受到限制和混乱,无论是视图快速或有效地到达任何地方。IBM Web 服务副总裁 Michael Liebow 表示 SOA 是“建设的高速公路”。[25]

-

SOA元模型,The Linthicum Group,2007

-

在某些方面,SOA可以被视为架构演变而不是革命。它捕获了以前软件架构的许多最佳实践。例如,在通信系统中,很少开发使用真正静态绑定与网络中的其他设备通信的解决方案。通过采用 SOA 方法,此类系统可以将自己定位为强调定义明确,高度可互操作的接口的重要性。SOA 的其他前身包括基于组件的软件工程和远程对象的面向对象分析和设计(OOAD),例如,在 CORBA 中。

-

服务包括仅通过正式定义的页面可用的独立功能单元。服务可以是某种易于生产和改进的“纳米企业”。服务也可以是作为子下属服务的协调工作而构建的“大型企业”。SOA 的成熟部署有效地定义了组织的 API。

-

将服务实施视为大型项目的单独项目的原因包括:

-
    -
  1. 分离将业务概念推广到业务,即服务可以快速独立地从组织中常见的较大且移动较慢的项目中提供。业务开始了解回调服务的系统和简化的用户界面。这提倡敏捷。也就是说,它促进了业务创新并加快了产品上市时间[26]
  2. -
  3. 分离促进了服务于消费项目的脱钩。这样可以鼓励良好的设计,因为服务的设计不需要知道消费者是谁。
  4. -
  5. 服务的文档和测试文件未嵌入较大项目的详细信息中。当服务需要在后续需要重用时,这很重要。
  6. -
-

SOA 承诺间接简化测试。服务是自治的,无状态的,具有完全记录的接口,并且与实现的关注点是分开的。如果组织拥有适当定义的测试数据,则会构建响应的存根,以便在构建服务时对测试数据做出反应。可以构建测试环境,其中原始和超出范围的服务是存根,而网格的其余ubuf 是完整服务的测试部署。由于每个接口都有完整的文档,并附有完整的回归测试文档,因此可以轻松识别测试服务中的问题。测试演变为仅仅验证测试服务是否根据其文档运行,并发现环境中所有服务的文档和测试用例存在的差距。管理幂等服务的数据状态是唯一的复杂性。

-

实例可能有助于将服务记录到有用的级别。Java community Process 中的一些 API 文档提供了良好的示例。由于这些是详尽无遗漏的,工作人员通常只使用重要的子集。JSR-89 的 ossjsa.pdf 文件中举例说明了这样做一个文件[27]

-

批评

-

SOA 已与 Web 服务混淆[28],但是,Web 服务只是实现构成 SOA 风格的模式的一种选择。在非本机或二进制形式的远程过程调用(RPC)的情况下,应用程序可能运行的更慢并且需要更多地处理能力,从而增加了成本。大多数实现都会产生这些开销,但 SOA 可以使用技术实现(例如,Java Business Integration(JBI)Windows Communication Foundation(WCF)data distribution service(DDS)),它们不依赖与远程过程调用或通过 XML 进行转换。与此同时,新兴的开源 XML 解析技术(如 VTD-XML)和各种 XML 兼容的二进制格式有望显著提高 SOA 性能。使用 JSON 而不是 XML 实现的服务不会受到这种性能的问题的影响[29] [30] [31]

-

有状态服务要求消费者和提供者共享相同的特定于消费者的上下文,该上下文包含在提供者和消费者之间交换的消息中或由其引入。如果服务提供者需要为每个消费者保留共享上下文,则此约束的缺点是它可能会降低服务提供者的整体可伸缩性。它还增加了服务提供者和消费者之间的耦合,使交换服务提供者更加困难[32]。最终,一些批评者认为 SOA 服务仍然受到他们所代表的应用程序的限制[33]

-

SOA 的体系结构面临的主要挑战是管理元数据。基于 SOA 的环境包括许多彼此之间进行通信以执行任务的服务。由于设计可能涉及多个服务一起工作,因此应用程序可能会产生数百万条消息。进一步的服务可能属于不同的组织甚至是竞争公司,造成巨大的信任问题。因此 SOA 治理进入了事务的计划[34]

-

SOA 面临的另一个主要问题是缺乏统一的测试架构。没有工具可以提供在 SOA 的体系结构中测试这些服务所需的功能。困难的主要原因是:[35]

-
    -
  • 异质性和解决方案的复杂性
  • -
  • 由于自主服务的集成,大量的测试组合
  • -
  • 包含来自不同和竞争供应商的服务
  • -
  • 由于新功能和服务的可用性,平台不断变化
  • -
-

请参阅 drops.dagstuhl.de[36],了解有关软件服务工程的其他挑战,部分解决方案和研究路线图

-

扩展和变体

-

事件驱动的体系结构

-

主要文章:事件驱动的架构

-

Web2.0

-

Tim O’Reilly 创造了“Web2.0”一词来描述一种快速增长的基于网络的应用程序[37]。经历了广泛报道的主题涉及 Web2.0与 SOA 体系机构之间的关系。

-

SOA 是将应用程序逻辑封装在具有统一定义的接口的服务中并通过发现机制公开可用的哲学。复杂性-隐藏和重用的概念,以及松耦合服务的概念,激发了研究人员详细阐述两种哲学,SOA 和 Web2.0及其各自应用之间的相似性。一些人认为 Web2.0和 SOA 具有显著不同的元素,因此不能被视为“平行哲学”,而其他人认为这两个概念是互补的,并将 Web2.0视为全球 SOA[38]

-

Web2.0和 SOA 的理念满足了不同的用户需求,从而暴露了设计方面的差异以及实际应用中使用的技术。但是,截止 2008 年,用例展示了结合 Web2.0和 SOA 技术和原则的潜力[38:1]

-

微服务

-

主要文章:微服务

-

微服务是对用于构建分布式软件系统的面向服务的体系结构的现代解释。微服务架构[39]中的服务是通过网络互相通信以实现目标的过程。这些服务使用技术不可知协议[40],这有助于封装语言和框架的选择,使他们的选择成为服务内部的一个问题。微服务是 SOA 的一种新的实现方法,自 2014 年(以及 DevOps 推出以后)开始流行,并且强调持续部署和其他的=敏捷实践[41]

-

微服务没有一个共同商定的定义。在文献中可以找到以下特征和原理:

-
    -
  • 细粒度接口(可独立部署的服务)
  • -
  • 业务驱动的开发(例如域驱动设计)
  • -
  • IDEAL 云应用架构
  • -
  • 多语言编程和持久化存储
  • -
  • 轻量级容器部署
  • -
  • 分散的持续交付
  • -
  • DevOps 提供全面的服务监控
  • -
-

其他

-
    -
  • 松耦合
  • -
  • OASIS SOA 参考模型
  • -
  • 服务粒度原则
  • -
  • SOA 治理
  • -
  • 软件架构
  • -
  • 面向服务的通信
  • -
  • 面向服务的应用程序开发
  • -
  • 面向服务的分布式应用程序
  • -
-

参考

-
-
-
    -
  1. Chapter 1: Service Oriented Architecture (SOA)”. msdn.microsoft.com. Archived from the original on February 6, 2016. Retrieved September 21, 2016. ↩︎

    -
  2. -
  3. Service-Oriented Architecture Standards - The Open Group”. www.opengroup.org. ↩︎

    -
  4. -
  5. What Is SOA?”. www.opengroup.org. Archived from the original on August 19, 2016. Retrieved September 21, 2016. ↩︎

    -
  6. -
  7. Velte, Anthony T. (2010). Cloud Computing: A Practical Approach. McGraw Hill. ISBN 978-0-07-162694-1. ↩︎

    -
  8. -
  9. Migrating to a service-oriented architecture, Part 1”. December 9, 2008. Archived from the original on December 9, 2008. Retrieved September 21, 2016. ↩︎

    -
  10. -
  11. Michael Bell (2008). “Introduction to Service-Oriented Modeling”. Service-Oriented Modeling: Service Analysis, Design, and Architecture. Wiley & Sons. p. 3. ISBN 978-0-470-14111-3. ↩︎

    -
  12. -
  13. Thomas Erl (June 2005). About the Principles. Serviceorientation.org ↩︎

    -
  14. -
  15. Application Platform Strategies Blog: SOA is Dead; Long Live Services”. Apsblog.burtongroup.com. January 5, 2009. Retrieved August 13, 2012. ↩︎

    -
  16. -
  17. Yvonne Balzer Improve your SOA project plans, IBM, July 16, 2004 ↩︎

    -
  18. -
  19. Microsoft Windows Communication Foundation team (2012). “Principles of Service Oriented Design”. msdn.microsoft.com. Retrieved September 3, 2012. ↩︎

    -
  20. -
  21. Principles by Thomas Erl of SOA Systems Inc. eight specific service-orientation principles ↩︎

    -
  22. -
  23. M. Hadi Valipour; Bavar AmirZafari; Kh. Niki Maleki; Negin Daneshpour (2009). “A brief survey of software architecture concepts and service oriented architecture”. 2009 2nd IEEE International Conference on Computer Science and Information Technology. pp. 34–38. doi:10.1109/ICCSIT.2009.5235004. ISBN 978-1-4244-4519-6. ↩︎

    -
  24. -
  25. Tony Shan (2004). “Building a service-oriented e Banking platform”. IEEE International Conference on Services Computing, 2004. (SCC 2004). Proceedings. 2004. pp. 237–244. doi:10.1109/SCC.2004.1358011. ISBN 978-0-7695-2225-8.2004 ↩︎

    -
  26. -
  27. Duan, Yucong; Narendra, Nanjangud; Du, Wencai; Wang, Yongzhi; Zhou, Nianjun. “Exploring Cloud Service Brokering from an Interface Perspective”. IEEE. ↩︎

    -
  28. -
  29. Duan, Yucong. “A Survey on Service Contract”. IEEE. ↩︎

    -
  30. -
  31. Olaf Zimmermann, Cesare Pautasso, Gregor Hohpe, Bobby Woolf (2016). “A Decade of Enterprise Integration Patterns”. IEEE Software. 33 (1): 13–19. doi:10.1109/MS.2016.11. ↩︎

    -
  32. -
  33. Rotem-Gal-Oz, Arnon (2012). SOA Patterns. Manning Publications. ISBN 978-1933988269. ↩︎

    -
  34. -
  35. K. Julisch et al., Compliance by Design – Bridging the Chasm between Auditors and IT Architects. Computers & Security, Elsevier. Volume 30, Issue 6-7, Sep.-Oct. 2011. ↩︎

    -
  36. -
  37. Brandner, M., Craes, M., Oellermann, F., Zimmermann, O., Web Services-Oriented Architecture in Production in the Finance Industry, Informatik-Spektrum 02/2004, Springer-Verlag, 2004 ↩︎

    -
  38. -
  39. www.ibm.com”. Retrieved September 10, 2016. ↩︎

    -
  40. -
  41. SOAP Version 1.2 の公開について (W3C 勧告)” (in Japanese). W3.org. Retrieved August 13, 2012. ↩︎

    -
  42. -
  43. Okishima, Haruhiru (2006). “. “Case Study of System Architecture that use COBOL assets”” (PDF). ↩︎

    -
  44. -
  45. Christopher Koch A New Blueprint For The Enterprise, CIO Magazine, March 1, 2005 ↩︎

    -
  46. -
  47. Enterprise SOA. Prentice Hall, 2005 ↩︎

    -
  48. -
  49. Elizabeth Millard (January 2005). “Building a Better Process”. Computer User. Page 20. ↩︎

    -
  50. -
  51. Brayan Zimmerli (November 11, 2009) Business Benefits of SOA, University of Applied Science of Northwestern Switzerland, School of Business ↩︎

    -
  52. -
  53. JSR-000089 OSS Service Activation API Specification 1.0 Final Release. sun.com ↩︎

    -
  54. -
  55. Joe McKendrick. “Bray: SOA too complex; ‘just vendor BS’”. ZDNet. ↩︎

    -
  56. -
  57. Jimmy Zhang (February 20, 2008) “Index XML Documents with VTD-XML”. XML Journal. ↩︎

    -
  58. -
  59. Jimmy Zhang (August 5, 2008) “i-Technology Viewpoint: The Performance Woe of Binary XML”. Microservices Journal. ↩︎

    -
  60. -
  61. Jimmy Zhang (January 9, 2008) “Manipulate XML Content the Ximple Way”. devx.com. ↩︎

    -
  62. -
  63. The Reason SOA Isn’t Delivering Sustainable Software”. jpmorgenthal.com. June 19, 2009. Retrieved June 27, 2009. ↩︎

    -
  64. -
  65. SOA services still too constrained by applications they represent”. zdnet.com. June 27, 2009. Retrieved June 27, 2009. ↩︎

    -
  66. -
  67. Governance Layer”. www.opengroup.org. Retrieved September 22, 2016. ↩︎

    -
  68. -
  69. How to Efficiently Test Service Oriented Architecture | WSO2 Inc”. wso2.com. Retrieved September 22, 2016. ↩︎

    -
  70. -
  71. http://drops.dagstuhl.de/opus/volltexte/2009/2046/pdf/09021_abstracts_collection.2046.pdf ↩︎

    -
  72. -
  73. What Is Web 2.0”. Tim O’Reilly. September 30, 2005. Retrieved June 10, 2008. ↩︎

    -
  74. -
  75. Christoph Schroth & Till Janner (2007). “Web 2.0 and SOA: Converging Concepts Enabling the Internet of Services”. IT Professional 9 (2007), Nr. 3, pp. 36–41, IEEE Computer Society. Retrieved February 23, 2008. ↩︎ ↩︎

    -
  76. -
  77. Dragoni, Nicola; Giallorenzo, Saverio; Alberto Lluch Lafuente; Mazzara, Manuel; Montesi, Fabrizio; Mustafin, Ruslan; Safina, Larisa (2016). “Microservices: yesterday, today, and tomorrow”. arXiv:1606.04036v1 cs.SE. ↩︎

    -
  78. -
  79. James Lewis and Martin Fowler. “Microservices”. ↩︎

    -
  80. -
  81. Balalaie, A.; Heydarnoori, A.; Jamshidi, P. (May 1, 2016). “Microservices Architecture Enables DevOps: Migration to a Cloud-Native Architecture”. IEEE Software. 33 (3): 42–52. doi:10.1109/MS.2016.64. hdl:10044/1/40557. ISSN 0740-7459. ↩︎

    -
  82. -
-
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/06/23/springboot1/index.html b/2019/06/23/springboot1/index.html deleted file mode 100644 index d6b2982c9..000000000 --- a/2019/06/23/springboot1/index.html +++ /dev/null @@ -1,571 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(一) 初识 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(一) 初识 -

- - -
- - - - -

- -

从本篇文章开始,记录学习 SpringBoot 框架在实践,源码方面的知识,本节是第一篇,因此不涉及相关复杂知识的学习。众所周知,随着微服务的广泛流行,Spring 系列的 SpringBoot 和 SpringCloud 的应用也更受欢迎,那么请跟随我的脚本来一步步解开 SpringBoot 她神秘的面纱

-

熟悉后端服务开发的小伙伴,在使用 SpringBoot 时一定会有这样的感受,咦,以前繁琐的配置,现在都不用再去配置一大堆东西了,以前跑起来一个 demo,感觉真是千辛万苦,错一步就 game over,以前服务基本都是已 war 包的形式运行在 Tomcat 中,而现在,你基本不需要手动写太多的代码,一个应用服务就可以运行起来,其次现在应用基本已 jar 包方式直接运行,虽然本质还是运行在 Tomcat 中,但现在 jar 包中已经有了服务运行的基础环境,可以直接使用 jar 相关的运行命令就可以运行起服务。好了,废话了这么多,先看看我们如何运行起一个 DEMO 应用。

-

环境及版本

-
    -
  • SpringBoot Version:2.1.6.RELEASE
  • -
  • System:macOS Mojave
  • -
  • JDK Version:1.8
  • -
  • Gradle:5.4.1
  • -
  • IDE:IntelliJ IDEA
  • -
-
-

本系列应用使用如上环境,其次应用包管理,小伙伴可以选择自己熟悉的 Maven 进行管理,而这里都使用 Gradle 进行管理

-
-

Demo

-

Spring Initializr

-

为了让开发者快速上手,官方提供了一建生成 SpringBoot 项目,你按需选择你需要的依赖即可。操作步骤如下截图
-spring-initializ

-

IDEA Init

-

-

IDEA分为四步完成初始

-
    -
  1. 选择 Spring Initializr 初始化向导
  2. -
  3. 填写项目坐标信息,构建工具,版本,报名等
  4. -
  5. 选择需要的组件(会自动添加依赖)
  6. -
  7. 选择项目存放路径
  8. -
-

Spring 运行

-

命令

-

macOS or Linux

-
1
2
# 项目路径下(spring-start)
gradlew bootRun
-

Windows

-
1
2
# 项目路径下(spring-start)
./gradlew bootRun
-

运行说明

-

spring-running-logo

-

Spring 打包

-

jar 分析

-

springboot-deploy-jar-unzip

-

目录说明

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
project/
├── BOOT-INF/
│ ├── classes # 当前项目结果文件放置在 classes 路径下
│ │ │ └── application.properties # 项目中配置文件
│ │ ├── org/ # 项目中 java 路径下,编译成 class 文件路径
│ │ ├── static/ # 项目中 resources 路径下的静态文件夹
│ │ └── templates/ # 项目中 resources 路径下的模板文件夹
│ └── lib/ # 项目所依赖的第三方 jar(Tomcat,SpringBoot 等)
├── META-INF/
│ └── MANIFEST.MF # 清单文件,用于描述可执行 jar 的一些基本信息
└── org/springframework/boot/loader/ # jar 包启动相关的引导
├── archive/
├── data
├── ExectableArchiveLauncher.class
├── jar/
├── JarLauncher.class
├── LaunchedURLClassLoader.class
├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
├── Launcher.class
├── MainMethodRunner.class
├── PropertiesLauncher.class
├── PropertiesLauncher$1.class
├── PropertiesLauncher$ArchiveEntryFilter.class
├── PropertiesLauncher$PrefixMatchingArchiveFilter.class
├── PropertiesLauncher$ArchiveEntryFilter.class
├── util/
└── WarLauncher.class
-

MANIFEST.MF

-
1
2
3
4
5
6
7
Manifest-Version: 1.0                                       # 清单版本号
Start-Class: org.incoder.start.SpringbootStartApplication # 项目 main 方法所在的类
Spring-Boot-Classes: BOOT-INF/classes/ # 项目相关代码在打包后 jar 中的路径
Spring-Boot-Lib: BOOT-INF/lib/ # 项目中所依赖的第三方 jar 在打包后 jar 中的路径
Spring-Boot-Version: 2.1.6.RELEASE # 项目 SpringBoot 版本
Main-Class: org.springframework.boot.loader.JarLauncher # 当前 jar 文件的执行入口类(main 方法所在的类)
回车换行(在清单文件中,必须有,否则会出错)
-

org/springframework/……目录

-

项目中引入的第三方 jar 中并不包含org/springframework/boot/loader内容,那这个目录是从哪里来的呢?

-

寻找最终发现是项目中我们的build.gradle文件中,引入的org.springframework.boot:spring-boot-gradle-plugin依赖,而这个依赖位于classpath下,说明引入的这个插件 仅仅 是在项目构建时才起作用,当项目进行打包后,并不会把插件包打入到项目的依赖库中,也就是BOOT-INF/lib/路径下

-

如何去研究在org/springframework/boot/loader下的源码内容呢?
-最好的方式是在项目的依赖中导入org.springframework.boot:spring-boot-loader依赖

-
-

原则上,在项目开发过程中是不需要引入org.springframework.boot:spring-boot-loader依赖,这里只是为了方便阅读源码进行学习

-
-

Spring 其他

-

配置文件格式

-
    -
  • properties
  • -
  • 推荐 yml
  • -
-
-

配置文件学习可参考 SpringBoot(四)配置文件

-
-

常用命令

-

gradle tasks

-

表示获取当前工程可用的 gradle tasks 命令

-
Application tasks
-
    -
  • bootRun:Runs this project as a Spring Boot application.(以 bootJar 的形式运行当前项目)
  • -
-
Build tasks
-
    -
  • bootJar:Assembles an executable jar archive containing the main classes and their dependencies.(装配一个可执行的 jar(自包含的 jar 包,不依赖其他容器) 归档,这个归档 jar 中包含了所需的依赖以及主类等)
  • -
-
Run jar
-
1
java -jar jar-name.jar
-
Other
-
1
2
# 解压 jar 到当前 start 目录下
unzip start-0.0.1-SNAPSHOT.jar -d ./start
-
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/07/05/springboot2/index.html b/2019/07/05/springboot2/index.html deleted file mode 100644 index c4b285c9f..000000000 --- a/2019/07/05/springboot2/index.html +++ /dev/null @@ -1,573 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(二) 启动分析JarLauncher | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(二) 启动分析JarLauncher -

- - -
- - - - -

我们在开发过程中,使用 java -jar you-jar-name.jar 命令来启动应用,它是如何启动?以及它如何去寻找 .class 文件并执行这些文件?本节就带着这两个问题,让我们一层层解开 SpringBoot 项目的 jar 启动过程,废话不多说,跟着我的脚步一起去探索 spring-boot-load 的秘密。

-

SpringBoot(一)初识 已经解释了为什么在编译后的 jar 中根目录存在 org/springframework/boot/loader 内容,以及为了方便学习研究,我们需要在项目的依赖中导入 org.springframework.boot:spring-boot-loader 依赖。同时我们在解压的 you-jar-name.jar 文件中,查看对应的清单文件 MANIFEST.MF 内容,其中明确指出了应用的入口 org.springframework.boot.loader.JarLauncher 因此我们就从 JarLauncher 开始一步步深入

- -

spring-boot-loader-jarlauncher

-

结构

-

先用Diagrams来表述 JarLauncher 类之间的结构及方法等相关信息
-jarlauncher

-

从Diagrams可知

-
    -
  • 继承关系:JarLauncher extends ExecutableArchiveLauncher extends Launcher
  • -
  • 启动入口:JarLauncher main 方法
  • -
-
-

关于图上图标含义,这里就不再赘述,烦请移步 IntelliJ IDEA Icon reference

-
-

流程分析

-

jar规范

-

对于 Java 标准的 jar 文件来说,规定在一个 jar 文件中,我们必须要将指定 main.class 的类直接放置在文件的顶层目录中(也就是说,它不予许被嵌套),否则将无法加载,对于 BOOT-INF/class/ 路径下的 class 因为不在顶层目录,因此也是无法直接进行加载, 而对于 BOOT-INF/lib/ 路径的 jar 属于嵌套的(Fatjar),也是不能直接加载,因此 Spring 要想启动加载,就需要自定义实现自己的类加载器去加载。

-
-

关于 jar 官方标准说明请移步

-
    -
  • JAR File Specification
  • -
  • JAR (file format)
  • -
-
-

源码分析

-

main 方法

-

根据清单文件 MANIFEST.MFMain-Class 的描述,我们知道入口类就是 JarLauncher;先看下这个类的 javadoc 介绍

-
1
2
3
4
5
6
7
8
9
10
11
/**
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
* included inside a {@code /BOOT-INF/lib} directory and that application classes are
* included inside a {@code /BOOT-INF/classes} directory.
*
* 用于基于JAR的归档。这个启动程序假设依赖jar包含在{@code /BOOT-INF/lib}目录中,
* 应用程序类包含在{@code /BOOT-INF/classes}目录中
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
-

紧接着,要进行源码分析,那肯定是找到入口,一步步深入,那么对于 JarLauncher 就是它的 main 方法了

-
1
2
3
4
public static void main(String[] args) throws Exception {
// launch 方法是调用父类 Launcher 的 launch 方法
new JarLauncher().launch(args);
}
-

那我们去看一看 Launcher 的 launch 方法

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Launch the application. This method is the initial entry point that should be
* called by a subclass {@code public static void main(String[] args)} method.
*
* 启动一个应用,这个方法应该被初始的入口点,这个入口点应该是一个Launcher的子类的
* public static void main(String[] args)这样的方法调用
*
* @param args the incoming arguments
* @throws Exception if the application fails to launch
*/
protected void launch(String[] args) throws Exception {
// 1. 注册一些 URL的属性
JarFile.registerUrlProtocolHandler();
// 2. 创建类加载器(LaunchedURLClassLoader),加载得到集合要么是BOOT-INF/classes/
// 或者BOOT-INF/lib/的目录或者是他们下边的class文件或者jar依赖文件
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// 3. 启动给定归档文件和完全配置的类加载器的应用程序
launch(args, getMainClass(), classLoader);
}
-

getClassPathArchives 方法

-launch 方法的第一步的相关内容比较简单,这里不做过多说明,主要后面两步,我们先看第二步,创建一个类加载器(ClassLoader),其中 getClassPathArchives() 方法是一个抽象方法,具体的实现有(ExecutableArchiveLauncherPropertiesLauncher ,因为我们研究的 JarLauncher 是继承 ExecutableArchiveLauncher ,因此我们这里看 ExecutableArchiveLauncher 类中 getClassPathArchives() 方法的实现)我们要看看这个方法中它做了什么 -
1
2
3
4
5
6
7
8
9
10
11
@Override
protected List<Archive> getClassPathArchives() throws Exception {
// 得到一个Archive的集合(BOOT-INF/classes/)和(BOOT-INF/lib/)目录所有的文件
// a. this.archive 中当前类的 archive 是怎么来的?
// b. getNestedArachives()是如何获得一个嵌套的 jar 归档?
// c. this::isNestedArchive 这个方法引用它做了什么?
List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
// 一个事后处理的方法
postProcessClassPathArchives(archives);
return archives;
}
-this.archive 位于当前类 ExecutableArchiveLauncher 的构造方法中 -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public ExecutableArchiveLauncher() {
try {
// 调用 createArchive() 方法得到Archive
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

/////////////////////////////////////////////////////////////
// 紧接着我们查看 createArchive() 方法都做了什么 //
/////////////////////////////////////////////////////////////

// Launcher.class 中的 createArchive()方法
// 得到我们运行文件的Archive相关的信息
protected final Archive createArchive() throws Exception {
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
String path = (location != null) ? location.getSchemeSpecificPart() : null;
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
// 返回我们要执行的jar文件的绝对路径(java -jar xxx.jar中 xxx.jar的绝对路径)
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException("Unable to determine code source archive from " + root);
}
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
}
-

对于 getNestedArachives() 方法,它是 Archive 的接口

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Returns nested {@link Archive}s for entries that match the specified filter.
*
* 返回与过滤器相匹配的嵌套归档文件
*
* @param filter the filter used to limit entries
* @return nested archives
* @throws IOException if nested archives cannot be read
*/
List<Archive> getNestedArchives(EntryFilter filter) throws IOException;

/////////////////////////////////////////////////////////////
// 紧接着我们查看 getNestedArchives() 的实现 //
/////////////////////////////////////////////////////////////

// 这里的参数 EntryFilter类型中有一个 matches(Entry entry) 方法,
// 这也是this::isNestedArchive所对应的实际方法
@Override
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
List<Archive> nestedArchives = new ArrayList<>();
for (Entry entry : this) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
}
-

this::isNestedArchive 方法引用,我们查看 isNestedArchive 抽象方法

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Determine if the specified {@link JarEntry} is a nested item that should be added
* to the classpath. The method is called once for each entry.
*
* 确定指定的{@link JarEntry}是否是应该添加到类路径的嵌套项。对每个条目调用该方法一次
*
* @param entry the jar entry
* @return {@code true} if the entry is a nested item (jar or folder)
*/
protected abstract boolean isNestedArchive(Archive.Entry entry);

/////////////////////////////////////////////////////////////
// 紧接着我们查看 isNestedArchive() 实现 //
/////////////////////////////////////////////////////////////

// JarLauncher.class 中的 isNestedArchive()方法
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
// 如果是目录判断是不是BOOT-INF/classes/目录
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
// 如果是文件判断文件的前缀是不是BOOT-INF/lib/开头
return entry.getName().startsWith(BOOT_INF_LIB);
}
-

createClassLoader 方法

-

把符合条件的 Archives 作为参数传入到 createClassLoader() 方法,创建一个类加载器,我们跟进去,查看 createClassLoader() 方法

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* Create a classloader for the specified archives.
*
* 创建一个所指定归档文件的类加载器
*
* @param archives the archives
* @return the classloader
* @throws Exception if the classloader cannot be created
*/
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(archives.size());
// 遍历传进来的 archives,将每一个 Archive 的 URL(归档文件在磁盘上的完整路径)添加到 urls 集合中
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
//
return createClassLoader(urls.toArray(new URL[0]));
}


/**
* Create a classloader for the specified URLs.
*
* 创建指定 URL 的类加载器
*
* @param urls the URLs
* @return the classloader
* @throws Exception if the classloader cannot be created
*/
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
// 这里的 LaunchedURLClassLoader 是 SpringBoot loader 给我们提供的一个全新的类加载器
// 参数 urls 是 class 文件或者资源配置文件的路径地址
// 参数 getClass().getClassLoader() 是应用类加载器
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}


/**
* Create a new {@link LaunchedURLClassLoader} instance.
* @param urls the URLs from which to load classes and resources
* @param parent the parent class loader for delegation
*/
public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
-

super() 方法是调用父类的方法,这样一层层跟进去,最终到了 JDK 的 ClassLoader 类,它也是所有类加载器的顶类

-

launch 方法

-launch 方法的第二个参数,getMainClass() 是一个抽象方法 -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Returns the main class that should be launched.
* @return the name of the main class
* @throws Exception if the main class cannot be obtained
*/
protected abstract String getMainClass() throws Exception;

/////////////////////////////////////////////////////////////
// 紧接着我们查看 getMainClass() 实现 //
/////////////////////////////////////////////////////////////

@Override
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
// 获取到 Manifest 文件中属性为`Start-Class`对应的值,也就是当前项目工程启动的类的完整路径
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
-

接着我们看 launch 方法

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Launch the application given the archive file and a fully configured classloader.
*
* 加载指定存档文件和完全配置的类加载器的应用程序
*
* @param args the incoming arguments
* @param mainClass the main class to run
* @param classLoader the classloader
* @throws Exception if the launch fails
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
// 将应用的加载器换成了自定义的 LaunchedURLClassLoader 加载器,然后入到线程类加载器中
// 最终在未来的某个地方,通过线程的上下文中取出类加载进行加载
Thread.currentThread().setContextClassLoader(classLoader);
// 创建一个主方法运行器运行
createMainMethodRunner(mainClass, args, classLoader).run();
}

/**
* Create the {@code MainMethodRunner} used to launch the application.
*
* 创建一个 MainMethodRunner 用于启动这个应用
*
* @param mainClass the main class
* @param args the incoming arguments
* @param classLoader the classloader
* @return the main method runner
*/
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
-

返回一个 MainMethodRunner 对象,我们紧接着去看看这个对象,

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Utility class that is used by {@link Launcher}s to call a main method. The class
* containing the main method is loaded using the thread context class loader.
*
* 被 Launcher 使用来调用 main 方法的辅助类,使用线程类加载来加载包含 main 方法的类
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class MainMethodRunner {

private final String mainClassName;

private final String[] args;

/**
* Create a new {@link MainMethodRunner} instance.
* @param mainClass the main class
* @param args incoming arguments
*/
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}

// 关键方法
public void run() throws Exception {
// 获取到当前线程上下文的类加载器,实际就是 springboot 自定义的加载器(LaunchedURLClassLoader)
// 加载 this.mainClassName所对应的类,实际也就是清单文件中对应 Start-Class 属性的类
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
// 通过反射获取到 main 方法和参数
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
// 调用目标方法运行
// invoke 方法参数一:是被调用方法所在对象,这里为 null,原因是我们所调用的目标方法是一个静态方法
// invoke 方法参数二:被调用方法所接收的参数
mainMethod.invoke(null, new Object[] { this.args });
}

}
-

到此为止,invoke 方法成功调用,那么我们项目中的main 方法就执行了,这时我们的所编写的 springboot 应用就正式的启动了。那么关于 springboot 的 loader 加载过程已经分析完

-

总结

-

summary-jarlauncher

-

从 jar 规范的角度出发,我们深入分析了 springboot 项目启动的整个过程,这个过程到底对不对,我们口说无凭,需要实际检验我们分析
-首先,我们先思考,项目的应用启动入口是不是必须是 main.class 方法,以及为什么要默认这么做?
-其次,我们再思考,在编辑器中通过图标运行启动程序(或者是通过命令启动程序),比较将程序编译成 jar 包,然后通过命令启动程序他们之间是否相同,如果不同请解释为什么?

-

问题一

-

项目的应用启动入口可以不是 main.class 方法,只是为什么会默认为 main.class 方法,原因是在 springboot 的 MainMethodRunner类的 run 方法中,是固定写死的 main ,为什么要这么写,答案是,我们可以在编辑器中已右键或其他图标启动的方式快速启动 springboot 项目(就像是在运行一个 Java 的 main 方法一样,不再向之前需要乱七八糟各种的配置)。

-

问题二

-

答案是不相同,我们可以在项目的应用启动 main.class 方法中,打印出加载类 System.out.println(项目启动加载类 + SpringbootStartApplication.class.getClassLoader()); ,这样就可以检验我们的分析是否正确。分别使用两种不同的方式

-
    -
  • 方式一:在编辑器中之间运行(右键,或者控制台输入命令gradle bootRun)或者使用 IDEA 上的运行应用运行按钮,结果如下
    1
    项目启动加载类sun.misc.Launcher$AppClassLoader@18b4aac2
    -
  • -
  • 方式二:先编译成 jar 包,然后通过 java -jar build-name.jar 命令运行
    1
    项目启动加载类org.springframework.boot.loader.LaunchedURLClassLoader@439f5b3d
    -
  • -
-

通过打印出来的信息,可以验证我们的分析,方式一的运行,实际上是应用类加载器启动,而方式二是 spring-boot-load 包中自定义的 LaunchedURLClassLoader 来启动项目

-

在实际的生产开发中,有时我们的分析需要进行验证(或者找问题),而此时服务又部署在生成环境或者非本机上,通常用的方式是看应用的日志输出,在日志中去定位问题,而有时我们需要断点的方式去找问题,那该如何去操作呢?对于这个问题,在实际开发中是有方法去处理,请看下篇《SpringBoot(三) JDWP远程调用》

-

附录

-
    -
  • spring_boot_cloud(2)Spring_Boot打包文件结构深入分析源码讲解
  • -
  • 校验者•CeaserWang
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/07/11/springboot3/index.html b/2019/07/11/springboot3/index.html deleted file mode 100644 index bbcbfc340..000000000 --- a/2019/07/11/springboot3/index.html +++ /dev/null @@ -1,507 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(三) JDWP远程调用 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(三) JDWP远程调用 -

- - -
- - - - -

在 SpringBoot 系列的第二篇文章中,已经详细分析了 SpringBoot 的启动过程,那么这篇文章,我们通过源码调试的方式来验证我们的分析,首先我们在控制台中输入 java 命令,可用输出 JDK 给我们提供了一些命令,其中-agentlib命令就是本篇文章所介绍,用于我们进行源码调试

- -

springboot-java-agentlib
-我们继续查看-agentlib详细的命令说明,输入java -agentlib:jdwp=help 查看帮助文档
-springboot-java-agentlib-help

-

远程

-
1
2
3
# 在远程机器上添加代理模式的方式启动
# 使用 socket 协议来进行远程调试,当服务启动就开始在 6666 端口等待连接
java -agentlib:jdwp=transport=dt_socket,server=y,address=6666 -jar start-1.0-SNAPSHOT.jar
-

本机

-

在本机上,我们直接使用 IDEA 编辑器,新建一个 Remote 应用服务,运行,创建步骤如下 9 步骤
-springboot-java-remote

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/07/21/deploy-maven/index.html b/2019/07/21/deploy-maven/index.html deleted file mode 100644 index 156382124..000000000 --- a/2019/07/21/deploy-maven/index.html +++ /dev/null @@ -1,635 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -发布 jar 到 Maven中央仓库 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 发布 jar 到 Maven中央仓库 -

- - -
- - - - -

背景

-

和朋友一起维护的开源组织(我就是打个辅助,逃~),其中有一个系列的项目,这些项目统一通过 base 项目的 pom 文件管理这个系列项目依赖的第三方 jar,其他一些辅助项目(如:tools)项目主要是一些常用工具方法的封装,为了能让我们在不同机器,不同地点能够无缝切换,更重要的让使用的伙伴能以最简便的方式运行(避免不必要的配置),我们需要把通用的东西托管起来,那么就需要将这些配置依赖或辅助 jar 托管到 Maven中央仓库,话不多说,就跟着我的步骤来看看如何将 jar 发布到 Maven中央仓库

- -

准备

-
    -
  • Sonatype 账号
  • -
  • GPG
  • -
  • 需要发布的项目
  • -
-
-

这里以 Mac 系统演示

-
-

Sonatype

-

账号注册

-

Sonatype 账号注册地址:https://issues.sonatype.org/secure/Signup!default.jspa

-
-

记录好你的账号和密码,后续会用到

-
-

创建 issue

-

创建 issue 地址:https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&pid=10134

-

填写信息时,只填写必填项即可

-
    -
  1. Summary:简单的项目介绍,填写一下
  2. -
  3. Group Id:项目ID,用来定位应用 jar 的坐标,可参考官方说明
  4. -
  5. Project URL:项目主页地址
  6. -
  7. SCM url:Git仓库地址
  8. -
-
-

注意:

-
-
    -
  • Group Id -
      -
    • 无自己的域名:可以使用 Github,比如我的 GitHub 用户名是 BladeCode(也可以用你的组织名,如这里:twodragonlake),那么这里的Group Id应该填写 com.github.BladeCode,也可以使用 io.github.BladeCode
    • -
    • 有自己的域名:按照要求添加一条 TXT 的 DNS 解析,用来验证你的 Group Id
      -ossrh-domain
    • -
    -
  • -
  • 可参考:OSSRH-45597
  • -
-

验证 Group Id

-

根据你是否有自己的域名,有不同的方式来验证,上面的创建 issue 的注意中已经说明了,这里不啰嗦了,直接看下图
-ossrh-ticket

-

GPG

-
    -
  • Windows:Gpg4win
  • -
  • macOS:gpg
  • -
-

安装

-

macOS 为例

-
1
2
3
4
# 安装
brew install gpg
# 验证
gpg --version
-

生成秘钥对

-
1
gpg --gen-key
-

ossrh-gpg-key

-

这里用于生成秘钥的用户名和邮箱,可以和你的 Sonatype 账号不一样,记录密码,在部署时需要用到

-

上传秘钥

-
1
2
3
4
# 上传秘钥,最好带上端口号
gpg --keyserver hkp://pool.sks-keyservers.net:11371 --send-keys <密钥ID>
# 验证秘钥,最好带上端口号
gpg --keyserver hkp://pool.sks-keyservers.net:11371 --recv-keys <密钥ID>
-

ossrh-gpg-send

-

上传到其他服务器,命令同上,更换地址即可

-
    -
  • hkp://keyserver.ubuntu.com:11371
  • -
  • hkp://keys.gnupg.net:11371
  • -
-

如果你忘记了你刚刚生成的秘钥,可以使用下面的命令来查看本地生成的所有秘钥

-
1
gpg --list-keys
-

配置

-

maven 配置

-
    -
  • 查看路径
    -macOS 可以使用 IDEA 查看 maven 的路径,/usr/local/Cellar/maven/3.6.0/libexec/conf
    -ossrh-maven-local
  • -
  • 修改 settings.xml 文件
    -在 <servers> 标签内,添加如下配置
    1
    2
    3
    4
    5
    <server>
    <id>oss</id>
    <username>你注册的Sonatype账号</username>
    <password>密码</password>
    </server>
    -
  • -
-

pom 配置

-

配置你需要上传项目的 pom 文件

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>

<name>tdl-base</name>
<description>TwoDragonLake base pom</description>
<url>https://github.com/TwoDragonLake/tdl-base</url>

<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
</license>
</licenses>

<developers>
<developer>
<!-- 这里配置 gpg 生成秘钥时的用户名和邮箱 -->
<name>Jerry xu</name>
<email>incoder.xu@gmail.com</email>
</developer>
</developers>
<!-- 更改成你的项目信息 -->
<scm>
<tag>master</tag>
<url>https://github.com/TwoDragonLake/tdl-base.git</url>
<connection>scm:git:https://github.com/TwoDragonLake/tdl-base.git</connection>
<developerConnection>scm:git:https://github.com/TwoDragonLake/tdl-base.git</developerConnection>
</scm>

<profiles>
<profile>
<!-- 这个id有用的,用于发布命令 mvn clean deploy -P release(这个参数) -Dmaven.test.skip=true -->
<id>release</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<!-- Source -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<!-- Skip javadoc error -->
<!-- <configuration>
<failOnError>false</failOnError>
<doclint>none</doclint>
</configuration> -->
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Gpg Signature -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- 这个也是必须要的 以下两个<id>代码块中的id要与 setting.xml中的id一致 -->
<distributionManagement>
<snapshotRepository>
<id>oss</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>oss</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
</profile>
</profiles>
-

编译并部署

-
1
mvn clean deploy -P release -Dmaven.test.skip=true
-

然后在命令行的弹出中输入使用 gpg 命令生成秘钥时输入的密码,如果在命令行中没有弹框提示,那么可以在终端中输入export GPG_TTY=$(tty)命令,再次执行部署命令,部署完成参考下图提示
-ossrh-deploy

-

发布

-

编译构建验签

-

部署成功后,使用Sonatype登录 https://oss.sonatype.org网站,进行发布,在Build Promotion中选择Staging Repositories,然后选择对应你的groud idRepository,进行 close,这里的Close其实就是进行自动构建,进行验证
-ossrh-close

-

参看是自动化运行过程否有错误,正确如下截图没有错误提示,如果有错误提示,就按照提示内容进行处理
-ossrh-build

-

发布

-

构建成功无错误,后就可以发布了,其实发布和部署是一样的操作,只不过部署是进行Close,而发布是Release操作,此时会提示你发布成功后会删除Staging RepositoriesRepository 记录
-ossrh-release

-

查看

-

打开你的https://issues.sonatype.org,登录并查看,你的 issues 下,提示你,已经发布成功,稍后可以在https://search.maven.org中搜索到
-ossrh-deploy-success

-

搜索结果,可以查看到我们发布的包
-ossrh-search

-

异常

-

IDEA 中 not found

-

项目中引入的 jar,IDEA 中提示无法找到包,可以打开 IDEA 的 Preferences 中进行同步
-ossrh-idea-update

-

本地编译错误

-
    -
  • 无提示框提示输入密码
    -ossrh-local-build-comfrim
    -解决方法:export GPG_TTY=$(tty) 命令,重新编译
  • -
  • 无法连接sonatype
    -ossrh-local-sonatype
    -解决方法:查看settings.xml文件中 <server>标签中配置的 id 是否与项目pom文件的<distributionManagement> 标签下的 id 是否一致
  • -
-

sonatype 构建错误

-

查看构建过程中错误提示,我这里是应为无法验证签名,因此我将提示中的服务器地址,全部都再发布gpg的秘钥,注意地址开头是 hkp
-ossrh-build-error

-

附录

-

官方

-
    -
  • OSSRH Guide
  • -
  • 发布要求、规范
  • -
  • PGP签名使用
  • -
  • 发布项目文档
  • -
-

其他

-
    -
  • 发布 Maven 构件到中央仓库
  • -
  • 发布构件到Maven中央仓库
  • -
  • 如何发布Jar包到Maven Central Repository
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/07/28/springboot4/index.html b/2019/07/28/springboot4/index.html deleted file mode 100644 index 40074c762..000000000 --- a/2019/07/28/springboot4/index.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(四)配置文件 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(四)配置文件 -

- - -
- - - - -

关于 SpringBoot 配置文件,在之前的文章中已经提到配置文件格式,主要是两种格式的配置,这里并没有哪个配置写法一定优于另一种写法,对于配置文件名(application.yml 或者 application.properties),可以更改,为了减少不必要的麻烦,不建议修改,本篇文章以 yml 文件作为示例

- -

本篇文章示例代码见:springboot-config

-

YAML

-

YAML是JSON的超集,因此是用于指定分层配置数据的便捷格式。只要在类路径上有SnakeYAML库,SpringApplication类就会自动支持YAML作为属性的替代 。

-

语法规则

-
    -
  • 大小写敏感
  • -
  • 缩进(只能使用空格,空格数量不重要)表示层级
  • -
  • 注释用 # 符号
  • -
-

数据结构

-
    -
  1. 不可再分的单个的值,如数字,字符串等。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    env: dev
    crate-date: 2020
    is-mac: true

    # ~表示NULL值
    email: ~

    # 多行字符串可以使用 | 保留换行符,也可以使用 > 折叠换行
    # + 表示保留文字块末尾的换行,- 表示删除字符串末尾的换行
    message: |-
    hello world ${crate-date}

    # 单引号
    # 会转义特殊字符,特殊字符最终只是一个普通的字符串数据
    # 输出:mac \n catalina
    name1: 'mac \n catalina'

    # 双引号
    # 不会转义字符串里面的特殊字符,特殊字符会作为本身想表示的意思
    # 输出:mac
    # catalina
    name2: "mac \n catalina"
    -
  2. -
  3. 数组,一组按次序排列的值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 这种写法,必须有两层结构,而且第二层(language 名字)是必须满足 Java 属性字段命名规则
    list:
    language:
    - 'object-c'
    - 'swift'
    - 'c'
    # 或者行内写法
    list-program-languages: object-c, swift, c
    # SpEL 获取数组
    el:
    list: object-c, swift, c
    -
  4. -
  5. 对象,键值对的集合
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 对象
    object:
    name: Jerry
    age: 20

    # 或者行内写法
    persons: { name: Jerry, age: 20 }

    # Map
    map-object:
    map:
    key1: value1
    key2: value2

    # 或者行内写法
    mapObjects.maps: { key1: value1,key2: value2 }
    -
  6. -
  7. 随机数
    1
    2
    3
    4
    5
    # 随机数
    secret: ${random.value}
    number: ${random.int}
    bignumber: ${random.long}
    uuid: ${random.uuid}
    -
  8. -
  9. 默认值,占位符获取之前配置的值,如果没有可以是用:指定默认值
    1
    2
    bootapp.name: SpringBoot
    description: ${bootapp.name}是一个spring应用程序
    -
  10. -
-
    -
  1. : 号后面有一个空格
  2. -
  3. 对于复杂的数据结构(对象,List,Map),需要配套 @ConfigurationProperties 定义对应的对象
  4. -
-
-

配置

-

为了在配置自定义属性时,向配置 SpringBoot 属性自动提示的功能,导入如下的包

-
1
2
3
4
5
6
7
8
9
10
11
12
<!--导入配置文件处理器,配置文件进行绑定就会有提示-->
<!-- @ConfigurationProperties annotation processing (metadata for IDEs)
生成spring-configuration-metadata.json类,需要引入此类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
-

或者在 gradle 配置文件中添加依赖

-
1
2
compileOnly 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
-

如果任然无法自动提示,请查看你的编辑器 IDEA 中是否开启了 Annonation Processing
-idea-annotation-processors

-

定义配置

-

默认配置

-

Common application properties,是 SpringBoot 官方提供的一些默认配置属性说明,如果需要更改,只需要在 application.yml 文件中重写该属性即可,比如重新设置服务的启动端口为 9090

-
1
2
server:
port: 9090
-

自定义配置

-

首先在 application.yml 文件中根据上面所述的规则写法进行自定义字段的声明

-
1
2
3
myConfig:
maps:
key: value
-

配置的使用

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@Value@ConfigurationProperties@PropertySource
使用场景单一属性注入,注解写在类的属性上批量注入,注解写在类上加载自定义配置文件,用于静态类获取配置文件中定义的信息
松散语法不支持支持支持
SpEL支持不支持支持
JSR-303 数据校验 @Validated不支持支持支持
复杂类型(数组,Map,对象等)不完全支持(数组支持行内定义)支持支持
-

ConfigurationProperties

-

@ConfigurationProperties 注解是 SpringBoot提供的一种使用属性的注入方法,不仅可以方便的把配置文件中属性值与所注解类绑定,还支持松散绑定,JSR-303 数据校验等功能

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 使用 @ConfigurationProperties 配置属性必须是小写,多单词之间可用'-'连接
*
* @author : Jerry xu
* @since : 2020/3/29 16:26
*/
@Data
@Component
@ConfigurationProperties(prefix = "list")
public class ConfigListBean {

/**
* 这里不能再用 @Value("") 去加载,修饰当前的字段,
* 必须指定其属性名和配置文件中定义的名称一致
*/
private List<String> language;

}
-

Value

-

@Value 注解支持直接从配置文件中读取值,同时支持 SpEL 表达式,但是不支持复杂数据类型和数据验证

-

使用 @Value 获取配置文件中定义的值,通常其类是被 @Controller@Service@Component 等注解修饰,如果是一般普通的类(如一些工具类)并 不能 只接获取到配置文件中定义的值

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Data
@Component
public class GradleDataBean {

@Value("${env}")
private String env;
@Value("${crate-date}")
private Integer createDate;
@Value("${is-mac}")
private Boolean isMac;
@Value("${email}")
private String email;
@Value("${message}")
private String message;
@Value("${name1}")
private String name1;
@Value("${name2}")
private String name2;

@Value("${list-program-languages}")
private List<String> programLanguages;

/**
* 使用el表达式,获取定义数组
*/
@Value("#{'${el.list}'.split(',')}")
private List<String> programList;

@Value("${secret}")
private String secret;
@Value("${number}")
private Integer number;
@Value("${bignumber}")
private Long bigNumber;
@Value("${uuid}")
private String uuid;
}
-

PropertySource

-

@PropertySource 注解加载自定义配置文件,由于 @PropertySource 指定的文件会优先加载,所以如果在 applocation.properties 文件中存在相同的属性配置,会覆盖前者中对应的值,且 @PropertySource 不支持 yml 文件注入

-

多环境配置

-

在实际的开发中,不同环境对应不同的配置,因此我们需要根据环境来配置不同的项目配置信息,SpringBoot 也是支持我们进行多环境的配置,通常情况下,我们命名为 application-{profile}.properties/yml ,其中
-{profile}表示不同的环境,比如:dev(开发),prod(线上) 等

-

关于具体的多环境配置,可以参考文章 SpringBoot(五)多环境配置

-

配置文件加载顺序

-

Spring Boot 启动会扫描以下位置的配置文件(application.properties 或 application.yml) 作为Spring Boot 的默认配置文件

-
    -
  1. -file:./config/
  2. -
  3. -file:./
  4. -
  5. -classpath:/config/
  6. -
  7. -classpath:/
  8. -
-

加载顺序可以查看下图
-application-sort

-

优先级从高到低,高优先级的配置会覆盖低优先级的配置

-

参考

-
    -
  • 介绍两种SpringBoot读取yml文件中配置数组的方法
  • -
  • Spring的@Value可以注入复杂类型吗?今天教你通过@value注入自定义类型
  • -
  • Properties and Configuration
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/08/21/lombok/index.html b/2019/08/21/lombok/index.html deleted file mode 100644 index 8ba8016ad..000000000 --- a/2019/08/21/lombok/index.html +++ /dev/null @@ -1,694 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Lombok | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Lombok -

- - -
- - - - -

在实际开发过程中,不管是服务端(Java),还是客户端(Android)都需要创建对应的实例bean对象,用来实例化对象,在对需要实例化的对象中,通常需要写 setget 方法,字段少的时候,还能忍受,尤其当客户端字段多的时候,而且字段类型或者字段名称来回改动时,稍不注意,就很大机率修改不全面,就会造成一些隐藏bug,有没有不需要手动取写(快捷键生成)这些方法,当然有,本片文章,我们就来学习 Lombok

-

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
-Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more(Project Lombok是一个java库,可以自动插入编辑器并构建工具,为您的java增添色彩。 永远不要再写另一个getter或equals方法,使用一个注释,你的类有一个功能齐全的构建器,自动化你的日志记录变量等等),这是官方对Lombok的介绍,简单来讲就是通过注解的方式,代替一些重复性的代码,让代码更加简洁

- -

安装集成

-
    -
  • 编辑器(IDEA or Android Studio)安装 Lombok 插件,File -> Settings... -> Plugins -> 搜索 Lombok 并安装
  • -
  • 开启编辑器 Annotation Processors
    -
  • -
  • 项目集成Lombok依赖,项目按照不同的包管理, 按照对应方式添加包依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!-- gradle 集成 -->
    <!-- https://projectlombok.org/setup/gradle -->
    repositories {
    mavenCentral()
    }

    dependencies {
    implementationOnly 'org.projectlombok:lombok:1.18.8'
    annotationProcessor 'org.projectlombok:lombok:1.18.8'
    }

    <!-- maven集成 -->
    <!-- https://projectlombok.org/setup/maven -->
    <dependencies>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
    <scope>provided</scope>
    </dependency>
    </dependencies>
    -
  • -
-

稳定注解

-

Lombok提供稳定的注解,可以直接在生成环境使用,org.projectlombok.lomboklombok路径下

-

val

-

val 作为任何局部变量声明类型(包含 for语句声明),并且该类型会推断初始化表达式的类型,同时也是final的。

-
    -
  • 0.10版本加入该功能
  • -
  • 使用场景:局部变量声明
  • -
-
1
2
3
4
5
6
// x 将被推断为 double 类型,并且是final
val x = 10.0;
// y 将被推断为 ArrayList<String> 类型,并且是final
val y = new ArrayList<String>();
// z 将被转换为 final int z = 10;
val z = 10;
-
-

官方示例val

-
-

var

-

var 作为任何局部变量声明类型(包含 for语句声明),并且该类型会推断初始化表达式的类型。

-
    -
  • 1.16.20版本加入该功能
  • -
  • 使用场景:局部变量声明
  • -
-
1
2
// x 将被推断为double,转换为 double x = 10.0d;
var x = 10.0;
-
-

官方示例var

-
-

@NonNull

-

注解在 属性 上,会自动产生一个关于此参数的非空检查,如果参数为空,则抛出一个空指针异常,也会有一个默认的无参构造方法

-
    -
  • 0.11.10版本加入该功能
  • -
  • 支持字段,方法,参数,局部变量,枚举
  • -
-
-

官方示例@NonNull

-
-

@Cleanup

-

自动资源管理,安全地调用close()方法,确保通过调用 close() 方法清除你注释的变量声明,无论发生声明情况

-
    -
  • 支持局部变量
  • -
-
1
2
3
4
5
6
7
8
9
10
public void copyFile(String in, String out) throws IOException {
@Cleanup FileInputStream inStream = new FileInputStream(in);
@Cleanup FileOutputStream outStream = new FileOutputStream(out);
byte[] b = new byte[65536];
while (true) {
int r = inStream.read(b);
if (r == -1) break;
outStream.write(b, 0, r);
}
}
-
-

官方示例@Cleanup

-
-

@Getter/@Setter

-
    -
  • 注解在 属性 上;为单个属性提供 set/get 方法;
  • -
  • 注解在 上,为该类所有的属性提供 set/get 方法, 都提供默认构造方法
  • -
  • 等级可自己更改 PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE,默认Public
  • -
-
1
2
3
// 可手动设置字段的set方法为你需要的修饰类型
@Setter(AccessLevel.PROTECTED)
private String name;
-
-

官方示例@Getter/@Setter

-
-

@ToString

-

任何 定义都可以用 @ToString 注释,让lombok生成 toString() 方法的实现。默认情况下,它会按顺序打印您的类名以及每个字段,并以逗号分隔

-
    -
  • includeFieldNames,默认true:打印时包括每个字段的名称
  • -
  • callSuper,默认false:在输出中包含超类的实现的结果
  • -
  • doNotUseGetters,默认false:通常情况下,如果有可用的getter,那么就会调用它们。要禁止此操作并让生成的代码直接使用这些字段,请将其设置为true
  • -
  • onlyExplicitlyIncluded,默认false:仅包含用明确标记的字段和方法。通常,默认情况下包含所有(非静态)字段
  • -
-
1
2
3
4
5
6
7
8
// 排除该字段一同生成在 toString()方法中
@ToString.Exclude
private int id;
// 配置在toString中呈现此成员的行为;如果在方法上,请在输出中包含方法的返回值
// rank(默认为0):首先打印更高的等级。相同级别的成员按照它们在源文件中出现的顺序打印
// name(默认为""):默认为带注释的成员的 字段/方法 名称。如果名称等于默认包含字段的名称,则此成员将取代它
@ToString.Include(rank = 0, name = "")
private int id;
-
-

官方示例@ToString

-
-

@EqualsAndHashCode

-

注解在 上, 可以生成 equals、canEqual、hashCode 方法,与 @Data 相比,少了 toString() 方法,部分属性和 @ToString 注解相同

-
-

官方示例@EqualsAndHashCode

-
-

Constructor

-

按照定制生成构造函数

-
    -
  • @NoArgsConstructor:生成不带参数的构造函数
  • -
  • @RequiredArgsConstructor:生成带有必需参数的构造函数。参数是final字段和字段是具有约束的,例如@NonNull
  • -
  • @AllArgsConstructor:生成一个全参构造函数
  • -
-
-

官方示例Constructor

-
-

@Data

-

生成所有字段的 getter,一个有用的 toString 方法,以及检查所有 non-transient 字段的 hashCode 和 equals 实现。还将为所有 non-final 字段以及构造函数生成setter

-
-

官方示例@Data

-
-

@Value

-

@Value 是 @Data 的变体版,默认情况下,所有字段都是 privatefinal 的,并且不会生成setter,默认情况下,class 本身也是 final 的,因为不可变性不是可以强制进入子类的东西

-
    -
  • 0.12.0版本加入稳定的该功能
  • -
-
-

官方示例@Value

-
-

@Builder

-
    -
  • 对成员注解,则它必须是构造函数或方法
  • -
  • 对类(class)注解,那么将生成一个私有构造函数,所有字段都作为参数(就好像类上有@AllArgsConstructor(access = AccessLevel.PRIVATE)),并且这个构造函数已经被@Builder注释了
  • -
-
-

注意,只有当您没有编写任何构造函数,也没有添加任何显式的@XArgsConstructor注释时,才会生成这个构造函数。在这些情况下,lombok将假定存在一个all-args构造函数,并生成使用它的代码;这意味着如果没有这个构造函数,就会出现编译器错误。

-
-
-

官方示例@Builder

-
-

@SneakyThrows

-
    -
  • 捕获或抛出方法主体中语句声明它们生成的任何已检查异常
  • -
  • SneakyThrows不会下沉,封装到RuntimeException中,或者以其他方式修改列出的已检查异常类型的任何异常
  • -
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// lombok 写法
@SneakyThrows(UnsupportedEncodingException.class)
public void utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}

// 等价于下面写法
public void utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException $uniqueName) {
throw useMagicTrickeryToHideThisFromTheCompiler($uniqueName);
// This trickery involves a bytecode transformer run automatically during the final stages of compilation;
// there is no runtime dependency on lombok.
}
}
-
-

官方示例@SneakyThrows

-
-

@Synchronized

-

@Synchronized几乎与将“synchronized”关键字放在方法上完全一样,只不过它将同步到一个私有内部对象上,这样其他不在您控制之下的代码就不会通过锁定自己的实例来干扰线程管理

-
    -
  • 对于 非静态 方法,使用一个名为 $lock 的字段
  • -
  • 对于 静态 方法,则注释会锁定名为 $LOCK 的静态字段
  • -
-
-

官方示例@Synchronized

-
-

@Getter(lazy=true)

-

适用于那些计算占用大量 CPU,或者占用较大内存时,该注解很有用

-
-

官方示例@GetterLazy

-
-

@Log

-

注解路径lombok.extern.java路径下,相关的 Log 有已下几种

-
    -
  • @CommonsLog
    1
    private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
    -
  • -
  • @Flogger
    1
    private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass();
    -
  • -
  • @JBossLog
    1
    private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
    -
  • -
  • @Log
    1
    private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
    -
  • -
  • @Log4j
    1
    private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
    -
  • -
  • @Log4j2
    1
    private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
    -
  • -
  • @Slf4j
    1
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
    -
  • -
  • @XSlf4j
    1
    private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
    -
  • -
-
-

官方示例@log

-
-

实验注解

-

Lombok提供实验性注解,请根据实际情况取舍,org.projectlombok.lomboklombok.experimental路径下

-

@Accessors

-

@ExtensionMethod

-

@FieldDefaults

-

@Delegate

-

@Wither

-

onX

-
    -
  • onMethod
  • -
  • onConstructor
  • -
  • onParam
  • -
-

UtilityClass

-

Helper

-

FieldNameConstants

-

SuperBuilder

-

进阶配置

-

Configuration system

-

其他

-

如果在对Android项目进行升级使用 Lombok 代替原来的写法,在很大程度上还是会遇到如下截图问题
-lombok-error
-根据提示项目已经开启了 Annotation Processors,但是在每次打开项目都会提示错误信息

-

解决方法

-

Setting for all projects

-
    -
  1. File -> Other Settings -> Settings for new projects -> Build, Execution, Deployment -> Compiler ->Annotation Processors
  2. -
  3. Enable Annotation Processing
  4. -
  5. Click Apply
  6. -
  7. Restart Your Android studio
  8. -
-

一些旧项目还需要额外的一些操作

-
    -
  1. 删除项目根路径下的 yourProject.iml 文件以及 .idea 目录 或者你可以 File -> Invalidate Caches / Restart… 操作
  2. -
  3. 重新打开项目
  4. -
-
-

参考issues264

-
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/09/12/gdd-2019/index.html b/2019/09/12/gdd-2019/index.html deleted file mode 100644 index 5881952c3..000000000 --- a/2019/09/12/gdd-2019/index.html +++ /dev/null @@ -1,579 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Google Developer Days 2019 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Google Developer Days 2019 -

- - -
- - - - -

-

连续三年申请参加 Google Developer Days,今年终于中签了,而且和好友大蛇丸及公司同事同时中签(可能是我们都使用了忍术)。嗯,终于离404公司又进了一步,哈哈哈~
-废话不啰嗦了,这篇文章就唠唠参加 GDD 的前前后后。

- -

众所周知每年 Google 会在 5 月份上旬在美国举行 Google 1/O (全球开发者大会)大会,在大会上无例外的推出新版本的 Android 系统(虽然只是 bate版本,今年 Android 取消了过去使用甜品命名的方式,而直接采用阿拉伯数字命名)等等软硬件上的技术探索和研究成果,给 Android 领域确立风向标。

-

而在中国,大概每年 9 月份会在中国上海举办 Google Developer Days,今年是第 4 年,可见 Google 对中国市场的重视。

-
    -
  • 2019
  • -
  • 2018
  • -
  • 2017
  • -
  • 2016
  • -
-

申请

-

由于 GDD 是不收取门票的,因此会对申请用户进行筛选,这个就要看运气了,可以通过以下的渠道获取信息,进行申请

-
    -
  • 微信公众号
    -
  • -
  • 微博 Google开发者
  • -
  • 知乎 谷歌开发者
  • -
  • 社区 GDG
  • -
  • 其他渠道
  • -
-

申请时需要填写一些资料,如实填写即可,剩下就是静待消息,如果审核通过,会发送邮件/短信通知你,不同的同学接收到的时间可能不同,具体的截止时间,以官方通知为准,没有通过的可查看官方合作的直播平台进行直播观看

-
-

众所周知,参加大会的基本是清一色的男同学,因此今年 Google 还专门有为女同学们提供了 1000 名的直通车,具体请移步官方公众号

-
-

参加

-

筛选通过后,那就是自己安排好自己的工作或者是学习,因为大会时间不一定是周末,以及安排好你的行程和住宿(两天的午餐都是由 GDD 提供)。我在杭州,因此就搭乘动车当天早上抵达上海虹桥,换乘地铁抵达目的地(上海世博中心)。由于支付宝并不支持上海地铁,因此需要提前下载一款 "Metro 大都会"应用

-

感受

-

满满当当两天下来,收货不少,这一届可以通过官方日程看出,重点是 Flutter 以及 TensorFlow 相关,大部分内容都是偏大前端这个领域,不管是相关应用场景的尝试还是一些技术细节和技术的巧妙实现,都能看得出 Google 在技术领域的话语权,其中有两个技术探索以及一场《挖掘事业发展潜力 - 开拓自己的道路》课堂,各位老师对职业发展讲解让我印象深刻

-
    -
  • 与 AR 相结合的 AR 导航(与滴滴合作),解决室内定位问题
  • -
  • 与艺术(音乐)结合,让技术有了温度,通过深度学习
  • -
  • 开拓自己的道路 -
      -
    • 对自己的专业技能需要达到融会贯通
    • -
    • 要主动的心态去工作,有企业家的精神
    • -
    • enjoy 的方式去对待自己所做的决定
    • -
    • 只有自己了解自己,才能将自己的推向更高的舞台
    • -
    -
  • -
-

另外通过现场感受,可以看到活动的现场屏幕边框元素是Material Desing中的,三角,圆,矩形,线条,和现场灯光融为一体,每一个视频动画都看得出他们在背后的付出,每一段音乐都那么的契合场景,这是我参加众多线下交流会,在现场感受最深的一次

-

其他

-

我们来看一看来自官方的活动精彩瞬间

- -

如何提高中签率

-

在知识星球中,看到有人分享

-

“简单说下对筛选的看法吧,报名的问卷非常的简单,都是一些有没有使用谷歌服务的选项。作为主办方,怎么样才能快速高效的在这之中找到自己想要的人呢?

-

这其实就是如何帮助谷歌建立你的用户画像,如果谷歌能找到更多的有利的信息,那么成功报名的机率自然会高。

-

那如何做到这点呢?其实很简单,提供使用谷歌服务频率最高最深的邮箱,因为谷歌可以很方便的获取到想要的信息!”

-

如何回顾

-

错过了现场参与,和视频直播,还能不能观看,答案是当然可以,官方会对直播视频进行剪辑,发布到bilibili视频网站,你可以关注Google中国 官方账号方便你第一时间活动更新动态,截止目前为止已发布,随后发布的我会及时更新

-
    -
  • -

    谷歌开发者大会开幕主旨演讲

    -
  • -
  • -

    移动端

    -
      -
    • Android 开发最新技术概览
    • -
    • Android 10 和隐私保护:使您的应用顺应变更
    • -
    • Android 无障碍:服务所有人
    • -
    • 利用 Kotlin 进行 Android 开发
    • -
    • 如何组装你的 Jetpack
    • -
    • CameraX:面向开发者的摄像头支持库
    • -
    • 移动Web技术拓展无限商机
    • -
    • AdMob 广告政策和工具
    • -
    • 用谷歌的新数据技术挖掘 App 变现潜力
    • -
    • ConstraintLayout + MotionLayout:打造丰富界面并为其制作动画效果
    • -
    • Material Theming:利用 Material 组件以极具表现力的方式构建主题背景
    • -
    • 利用 Material Design 设计深色主题背景
    • -
    -
  • -
  • -

    机器学习

    -
      -
    • 机器学习简介
    • -
    • 机器学习赋能智慧营销,成就商业新增长
    • -
    • 利用基准化分析和剖析功能提升应用性能
    • -
    -
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/09/25/flowable1/index.html b/2019/09/25/flowable1/index.html deleted file mode 100644 index b0576ab2a..000000000 --- a/2019/09/25/flowable1/index.html +++ /dev/null @@ -1,567 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Flowable(一)初识 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Flowable(一)初识 -

- - -
- - - - -

Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据等,众所周知,Flowable是Activit的一个分叉,Flowable的第一个版本(5.22.0)是基于Activit(5.21.0),关于为什么Flowable会从Activit分叉,感兴趣可以查看Flowable官方的文章Flowable and Activiti: What the Fork?!,这里不在赘述这些内容

- -

Flowable官方文档介绍,可知Flowable遵循BPMNCMMNDMNFrom设计指导

-
    -
  • BPMN(Business Process Model and Notation):用于流程管理
  • -
  • CMMN(Case Management Model and Notation):用于案例管理
  • -
  • DMN(Decision Model and Notation):用于决策规则
  • -
  • Form:用于表单和任务表单管理
  • -
-

Flowable 直运行

-
-

这里所说的"直运行",是指不需要写任何代码,仅需要改动相关的配置就可以运行起Flowable应用程序

-
-

准备工作

-
    -
  • Flowable v6.4.2
  • -
  • MySQL8+
  • -
  • JDK & Tomcat 环境
  • -
-
-

MySQL8+ ,JDK,Tomcat环境代建可参考Linux 之 MySQLLinux 常用应用安装Windows 之 常用应用安装

-
-

已下操作均在Windows上,macOS上相差不大,操作流程基本一致

-

war部署

-
    -
  1. 解压flowable.zip文件
    -flowable-zip
  2. -
  3. 拷贝需要启动的war到安装的Tomcat的webapps路径下
    -flowable-tomact
  4. -
  5. 命令行中执行startup.bat命令,或执行Tomcat的bin路径下,启动startup.bat文件
    -flowable-startup
  6. -
  7. 第一次启动,Tomcat控制台应该会出错,因为flowable-admin.war数据库配置默认使用H2数据库,我们需要修改数据库配置连接等信息
    -flowable-mysql-config -
    -
      -
    • 文件地址:<your tomcat path>/webapps/flowable-admin/WEB-INF/classes路径,flowable-default.properties文件及application-dev.properties文件
    • -
    • MySQL中需要一个名为 flowable 的数据库,没有请创建一个CREATE DATABASE flowable
    • -
    • 由于我使用的是 MySQL8 ,Tomcat 中不包含此驱动 jar 包,因此需要手动下载mysql-connector-java-8.x.x(和你数据库匹配版本).zip文件进行解压,拷贝mysql-connector-java-8.x.x.jar文件到 <your tomcat path>/lib路径下
    • -
    -
    -
  8. -
  9. 重新在命令行中执行startup.bat命令,或执行Tomcat的bin路径下,启动startup.bat文件
  10. -
  11. 正常情况到此等待服务器启动完成,如果不能正常启动,请查看Tomcat控制台是否有错误,按照提示解决错误,直到Tomcat不再有错误提示即可
  12. -
-

使用

-
    -
  1. 访问http://localhost:8080/flowable-idm,默认账号:admin,默认密码:test
    -flowable-admin
  2. -
  3. 访问http://localhost:8080/flowable-admin,后台管理
  4. -
  5. 访问http://localhost:8080/flowable-modeler,流程定义管理
  6. -
  7. 访问http://localhost:8080/flowable-task,用户任务管理
  8. -
  9. 访问http://localhost:8080/flowable-rest/docs,流程引擎对外提供的API接口
  10. -
-

Flowable 集成运行

-
-

这里所说的"集成运行",是指通过Flowable官方提供的jar文件,集成到我们的项目中运行的方式

-
-

Flowable 使用

-

其他

-

如何切换中文

-

Flowable中已包含中文语言,会根据操作系统语言,自动显示对应语言

-

startup.bat异常

-

查看控制它异常,例如当前flowable启动默认端口8080,被占用
-flowable-aleady-bind

-

解决方法:查找占用端口进程netstat -ano|findstr 端口号,并kill它taskkill -PID 进程号 -F
-flowable-kill-task

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/09/25/windows-devtool/index.html b/2019/09/25/windows-devtool/index.html deleted file mode 100644 index e4bccaf77..000000000 --- a/2019/09/25/windows-devtool/index.html +++ /dev/null @@ -1,597 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Windows 之 常用应用安装 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Windows 之 常用应用安装 -

- - -
- - - - -

这是一篇在Windows系统下,持续更新常用开发软件安装汇总,当然一些简单得安装就在这里记录,不废话了

-

JDK

-

官方下载地址,选择需要的版本下载安装包

-

安装完成,设置环境变量,右击我的电脑–>属性–>高级系统设置–>高级–>环境变量

- -
    -
  1. 在系统变量里新建 JAVA_HOME 变量,变量值为你的JDK的安装路径,比如:C:\Program Files\Java\jdk1.8.0_60
  2. -
  3. 在系统变量里新建 CLASSPATH 变量
    1
    .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
    -
  4. -
  5. 找到 path 变量(已存在不用新建)添加变量值
    1
    %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin
    -
  6. -
  7. 保存修改,并启动 CMD 进行验证,输出安装的 Java 版本表示安装成功
    1
    java -version
    -
  8. -
-
-

变量值之间用 ; 隔开。注意原来Path的变量值末尾有没有 ; 号,如果没有,先输入 ; 号再输入

-
-

Git

-
    -
  • 官方下载地址
  • -
  • 清华镜像下载地址
  • -
-

关于 Git 的安装没有什么可说的(使用默认配置即可),基本上就是下一步,下一步,到完成

-

MySQL

-
    -
  • 官方下载地址
  • -
  • 下载MySQL Community Server或者MySQL Installer for Windows都可以,这里我下载的是MySQL Community Server
    -
  • -
-

安装步骤请看截图所示
-windows-mysql-install

-

Tomcat

-

官方网站,选择需要的版本下载

-
    -
  • 官方 Tomcat 镜像:https://downloads.apache.org/tomcat/
  • -
  • 北京理工大学:https://mirrors.bit.edu.cn/apache/tomcat/
  • -
  • 北京外国语大学:https://mirrors.bfsu.edu.cn/apache/tomcat/
  • -
  • 清华大学:https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/
  • -
-

安装

-

下载对应版本的文件,这里比如 apache-tomcat-9.0.26 版本文件,放入你系统某个位置,最好是英文路径且路径中没有空格或特殊字符,并解压文件

-

配置

-

Tomcat 和 JDK 一样为了方便使用都需要配置环境变量,右击我的电脑–>属性–>高级系统设置–>高级–>环境变量

-
    -
  1. 在系统变量里新建 CATALINA_BASE 变量,变量值为你的Tomcat路径,比如:D:\DevTools\apache-tomcat-9.0.26
  2. -
  3. 在系统变量里新建 CATALINA_HOME 变量,变量值为你的Tomcat路径,比如:D:\DevTools\apache-tomcat-9.0.26
  4. -
  5. 找到 path 变量(已存在不用新建)添加变量值
    1
    ;%CATALINA_HOME%\lib;%CATALINA_HOME%\bin;
    -
    -

    变量值之间用 ; 隔开。注意原来Path的变量值末尾有没有 ; 号,如果没有,先输入 ; 号再输入

    -
    -
  6. -
  7. 保存修改,并启动 CMD 进行验证
    1
    statrup
    -
  8. -
-

乱码

-

Tomcat 控制台中中文乱码,需要修改 Tomcat 配置文件 logging.properties 中的字符编码,将默认的 UTF-8 改为 GBK,文件路径<You Tomcat>\conf
-windows-tomcat-encode

-

缓存

-

文件路径<You Tomcat>\conf,修改 context.xml 文件,在 <Context> 标签中添加如下配置即可

-
1
<Resources cachingAllowed="true" cacheMaxSize="100000" />
-

Maven

-

官方网站

-
    -
  • 官方 Maven 镜像:https://downloads.apache.org/maven/
  • -
  • 北京理工大学:https://mirrors.bit.edu.cn/apache/maven/
  • -
  • 北京外国语大学:https://mirrors.bfsu.edu.cn/apache/maven/
  • -
  • 清华大学:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/
  • -
-

安装

-

下载对应版本的文件,这里比如 apache-maven-3.6.3 版本文件,放入你系统某个位置,最好是英文路径且路径中没有空格或特殊字符,并解压文件

-

配置

-

Maven 和 JDK 一样为了方便使用都需要配置环境变量,右击我的电脑–>属性–>高级系统设置–>高级–>环境变量

-
    -
  1. 在系统变量里新建 MAVEN_HOME 变量,变量值为你的Tomcat路径,比如:D:\DevTools\apache-maven-3.6.3
  2. -
  3. 找到 path 变量(已存在不用新建)添加变量值
    1
    ;%MAVEN_HOME%\bin;
    -
    -

    变量值之间用 ; 隔开。注意原来Path的变量值末尾有没有 ; 号,如果没有,先输入 ; 号再输入

    -
    -
  4. -
  5. 保存修改,并启动 CMD 进行验证
    1
    mvn -version
    -
  6. -
-

镜像地址(可选)

-

如果你服务所依赖的包都是使用公司内部的私服,或者需要加快依赖的同步速度,那么建议你修改 maven 同步镜像的配置,编辑 <You maven>\conf 路径下 settings.xml 文件,在 标签下修改镜像地址

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云central仓库</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云jcenter-public仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云google仓库</name>
<url>https://maven.aliyun.com/repository/google</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云gradle-plugin仓库</name>
<url>https://maven.aliyun.com/repository/gradle-plugin</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云spring仓库</name>
<url>https://maven.aliyun.com/repository/spring</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云spring-plugin插件仓库</name>
<url>https://maven.aliyun.com/repository/spring-plugin</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云grails-core插件仓库</name>
<url>https://maven.aliyun.com/repository/grails-core</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云apache-snapshots仓库</name>
<url>https://maven.aliyun.com/repository/apache-snapshots</url>
</mirror>
</mirrors>
-

本地依赖存放地址

-

默认,maven 的依赖都存放在 C:Users/<Your name>/.m2/reposittory 路径下,你可以指定 maven 同步的依赖存默认放在你设置的路径下,编辑 <You maven>\conf 路径下 settings.xml 文件,修改 标签的内容

-
1
<localRepository>D:DevTools/maven/repository</localRepository>
-

配置修改验证

-

配置完成,在命令行输入 mvn help:system 测试,查看下载链接里面是否是现在配置的镜像地址,以及下载后的文件是否存放在自定设置的目标地址

-

CMD

-

常用命令

-
    -
  1. IP地址
    1
    ipconfig
    -
  2. -
  3. 查看端口
    1
    2
    3
    4
    5
    6
    # 查看端口号:netstat -ano | findstr 端口号
    netstat -ano | findstr 8080
    # 查看占用端口号进程 :tasklist | findstr 进程号
    tasklist | findstr 12836
    # kill 指定进程:taskkill -PID 进程号 -F
    taskkill -PID 12836 -F
    -截图如下:
    -windows-task
  4. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/10/20/bigdecimal/index.html b/2019/10/20/bigdecimal/index.html deleted file mode 100644 index 2c56ed5f0..000000000 --- a/2019/10/20/bigdecimal/index.html +++ /dev/null @@ -1,662 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -BigDecimal | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- BigDecimal -

- - -
- - - - -

float 和 double 同样也是可以表示浮点数,为啥在对于要求精确的进度计算时,尤其是关于币值相关,都采用 BigDecimal 类型来处理?

-
    -
  1. float 和 double 类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的 快速近似 计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该用于要求精确结果的场合。——《Effective Java》
  2. -
  3. float 精度 7 位,double 精度 16 位
  4. -
- -

综上所述,对于精确计算时,就不能采用 float 和 double 来计算了,而是 正确的使用 BigDecimal 时,结果才是精确的,为什么会这么说,那就跟着我一块来深入了解 BigDecimal,我们查看java.math路径下,除了 BigDecimal 还有 BigInteger ,因此,我们先去了解 BigInteger

-

BigInteger

-

Java 中,由 CPU 原生提供的整形最大范围是 64 位long类型整数。使用long类型整数可以直接通过 CPU 指令进行计算,速度非常快。如果使用的整数范围超过了long类型的范围怎么办?这时就只能用软件来模拟一个大整数。BigInteger表示不可变的任意精度的整数(继承Number)。BigInteger 内部用一个 int[] 数组来模拟一个非常大的整数

-

构造方法

-
    -
  • BigInteger(byte[] val):将包含 BigInteger 的二进制补码表示形式的 byte 数组转换为 BigInteger
  • -
  • BigInteger(int signum, byte[] magnitude):将 BigInteger 的符号-数量表示形式转换为 BigInteger。
  • -
  • BigInteger(int bitLength, int certainty, Random rnd):构造一个随机生成的正 BigInteger,它可能是一个具有指定 bitLength 的素数
  • -
  • BigInteger(int numBits, Random rnd):构造一个随机生成的 BigInteger,它是在 0 到 (2numBits - 1)(包括)范围内均匀分布的值
  • -
  • BigInteger(String val):将 BigInteger 的十进制字符串表示形式转换为 BigInteger,常用构造方法
  • -
  • BigInteger(String val, int radix):将指定基数的 BigInteger 的字符串表示形式转换为 BigInteger
  • -
-
1
2
3
BigInteger bi = new BigInteger("1234567890");
// 计算出 bi⁵ = 2867971860299718107233761438093672048294900000
System.out.println(bi.pow(5));
-

常用运算方法

-

对于加减乘除等运算,BigInteger 提供了对应的方法

-
1
2
3
4
5
6
7
8
9
10
BigInteger a = new BigInteger("1234567890");
BigInteger b = new BigInteger("9876543210");
// a+b = 11111111100
System.out.println("a+b = " + a.add(b));
// a-b = -8641975320
System.out.println("a-b = " + a.subtract(b));
// a*b = 12193263111263526900
System.out.println("a*b = " + a.multiply(b));
// a/b = 0
System.out.println("a/b = " + a.divide(b));
-

转换

-

long 类型整数运算比,BigInteger不会有范围限制,但缺点是速度比较慢。

-
1
2
3
4
5
BigInteger i = new BigInteger("123456789000");
// 123456789000
System.out.println(i.longValue());
// java.lang.ArithmeticException: BigInteger out of long range
System.out.println(i.multiply(i).longValueExact());
-
-

使用 longValueExact() 方法时,如果超出了 long 类型的范围,会抛出 ArithmeticException

-
-

BigIntegerIntegerLong 一样,也是不可变类,并且也继承自 Number 类。因为 Number 定义了转换为基本类型的几个方法:

-
    -
  • 转换为bytebyteValue()
  • -
  • 转换为shortshortValue()
  • -
  • 转换为intintValue()
  • -
  • 转换为longlongValue()
  • -
  • 转换为floatfloatValue()
  • -
  • 转换为doubledoubleValue()
  • -
-

通过上述方法,可以把 BigInteger 转换为基本类型。如果 BigInteger 表示的范围超过了基本类型,转换时将丢失高位信息,即结果不一定准确;因此,如果需要 准确的转换成基本类型,可以使用 intValueExact()longValueExact() 等方法,在转换时如果超出范围,将直接抛出 ArithmeticException异常

-

BigDecimal

-

BigDecimalBigInteger类似,BigDecimal 表示一个任意大小且精度完全准确的浮点数。BigDecimal 是由任意精度的整数非标度值(unscaled value)和 32 位的整数标度(scale)组成,通常用于币值的计算。

-

构造方法

-

BigDecimal 拥有16 个构造方法,常用如下三种

-
    -
  • BigDecimal BigDecimal(double d); // 不允许使用,精度不能保证
  • -
  • BigDecimal BigDecimal(String s); // 常用,推荐使用
  • -
  • static BigDecimal valueOf(double d); // 常用,推荐使用
  • -
-
1
2
3
4
5
6
7
8
9
10
11
12
BigDecimal bigDecimal = new BigDecimal(2);
BigDecimal bString = new BigDecimal("2.3");
BigDecimal bDouble = new BigDecimal(2.3);
BigDecimal bDouble1 = BigDecimal.valueOf(2.3);
// 输出:bigDecimal = 2
System.out.println("bigDecimal = " + bigDecimal);
// 输出:bString = 2.3
System.out.println("bString = " + bString);
// 输出:bDouble = 2.29999999999999982236431605997495353221893310546875
System.out.println("bDouble = " + bDouble);
// 输出:bDouble1 = 2.3
System.out.println("bDouble1 = " + bDouble1);
-
    -
  • 参数类型为 double 的构造方法的结果有一定的不可预知性;
  • -
  • 参数类型为 String 的构造方法的结果是完全可预知的,因此我们在编写时尽量都用 String 的构造方法
  • -
  • 当 double 必须用作 BigDecimal 的源时可以用 BigDecimal 的静态方法 value()
  • -
-

常用方法

-
1
2
3
4
5
6
7
8
9
10
11
12
BigDecimal a = new BigDecimal("1234567890.56789");
BigDecimal b = new BigDecimal("9876543210.01234");
// a+b = 11111111100.58023
System.out.println("a+b = " + a.add(b));
// a-b = -8641975319.44445
System.out.println("a-b = " + a.subtract(b));
// a*b = 12193263116887551591.2965077626
System.out.println("a*b = " + a.multiply(b));
// 报错:ArithmeticException,因为除不尽
// System.out.println("a/b = " + a.divide(b));
// 保留10位小数并四舍五入
System.out.println("a/b = " + a.divide(b, 10, RoundingMode.HALF_UP));
-

转换

-

BigInteger相同

-

舍入模式

-
    -
  • ROUND_CEILING:向 正无限大方向舍入 的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.UP;如果结果为负,则舍入行为类似于 RoundingMode.DOWN
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    5.5  =>  6 
    1.1 => 2
    -1.0 => -1
    -2.5 => -2
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("ROUND_CEILING模式:" + a1.setScale(0, RoundingMode.CEILING));
    System.out.println("ROUND_CEILING模式:" + a2.setScale(0, RoundingMode.CEILING));
    System.out.println("ROUND_CEILING模式:" + a3.setScale(0, RoundingMode.CEILING));
    System.out.println("ROUND_CEILING模式:" + a4.setScale(0, RoundingMode.CEILING));
    -
  • -
  • RoundingMode.DOWN:向 零方向舍入 的舍入模式。从不对舍弃部分前面的数字加 1(即截尾)。注意,此舍入模式始终不会增加计算值的绝对值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    5.5  =>  5 
    1.1 => 1
    -1.0 => -1
    -2.5 => -2
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("DOWN模式:" + a1.setScale(0, RoundingMode.DOWN));
    System.out.println("DOWN模式:" + a2.setScale(0, RoundingMode.DOWN));
    System.out.println("DOWN模式:" + a3.setScale(0, RoundingMode.DOWN));
    System.out.println("DOWN模式:" + a4.setScale(0, RoundingMode.DOWN));
    -
  • -
  • RoundingMode.FLOOR(此舍入模式始终不会增加计算值):向 负无限大方向舍入 的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.DOWN;如果结果为负,则舍入行为类似于 RoundingMode.UP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    5.5  =>  5 
    1.1 => 1
    -1.0 => -1
    -2.5 => -3
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("FLOOR模式:" + a1.setScale(0, RoundingMode.FLOOR));
    System.out.println("FLOOR模式:" + a2.setScale(0, RoundingMode.FLOOR));
    System.out.println("FLOOR模式:" + a3.setScale(0, RoundingMode.FLOOR));
    System.out.println("FLOOR模式:" + a4.setScale(0, RoundingMode.FLOOR));
    -
  • -
  • RoundingMode.HALF_DOWN:向 最接近数字方向舍入 的舍入模式,如果与两个相邻数字的距离相等,则向 下舍入 。如果被舍弃部分 > 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    5.5  =>  5
    1.1 => 1
    -1.1 => -1
    -2.5 => -2
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("HALF_DOWN模式:" + a1.setScale(0, RoundingMode.HALF_DOWN));
    System.out.println("HALF_DOWN模式:" + a2.setScale(0, RoundingMode.HALF_DOWN));
    System.out.println("HALF_DOWN模式:" + a3.setScale(0, RoundingMode.HALF_DOWN));
    System.out.println("HALF_DOWN模式:" + a4.setScale(0, RoundingMode.HALF_DOWN));
    -
  • -
  • RoundingMode.HALF_EVEN:向 最接近数字方向舍入 的舍入模式,如果与两个相邻数字的距离相等,则向 相邻的偶数舍入 。如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;如果为偶数,则舍入行为同 RoundingMode.HALF_DOWN
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    5.5  =>  6 
    1.1 => 1
    -1.0 => -1
    -2.5 => -2
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("HALF_EVEN模式:" + a1.setScale(0, RoundingMode.HALF_EVEN));
    System.out.println("HALF_EVEN模式:" + a2.setScale(0, RoundingMode.HALF_EVEN));
    System.out.println("HALF_EVEN模式:" + a3.setScale(0, RoundingMode.HALF_EVEN));
    System.out.println("HALF_EVEN模式:" + a4.setScale(0, RoundingMode.HALF_EVEN));
    -
  • -
  • RoundingMode.HALF_UP(此舍入模式就是通常学校里讲的四舍五入):向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。如果被舍弃部分 >= 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    5.5  =>  6
    1.1 => 1
    -1.1 => -1
    -2.5 => -3
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.1");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("HALF_UP模式:" + a1.setScale(0, RoundingMode.HALF_UP));
    System.out.println("HALF_UP模式:" + a2.setScale(0, RoundingMode.HALF_UP));
    System.out.println("HALF_UP模式:" + a3.setScale(0, RoundingMode.HALF_UP));
    System.out.println("HALF_UP模式:" + a4.setScale(0, RoundingMode.HALF_UP));
    -
  • -
  • RoundingMode.UNNECESSARY:用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。如果对生成精确结果的操作指定此舍入模式,则抛出 ArithmeticException
    1
    2
    3
    4
    5
    1.5  =>  抛出 ArithmeticException
    1.1 => 抛出 ArithmeticException
    1.0 => 1
    -1.1 =>抛出 ArithmeticException
    -1.6 => 抛出 ArithmeticException
    -
  • -
  • RoundingMode.UP:远离零方向舍入 的舍入模式。始终对非零舍弃部分前面的数字加 1。注意,此舍入模式始终不会减少计算值的绝对值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    5.5  =>  6 
    1.1 => 2
    -1.0 => -1
    -2.5 => -3
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("UP模式:" + a1.setScale(0, RoundingMode.UP));
    System.out.println("UP模式:" + a2.setScale(0, RoundingMode.UP));
    System.out.println("UP模式:" + a3.setScale(0, RoundingMode.UP));
    System.out.println("UP模式:" + a4.setScale(0, RoundingMode.UP));
    -
  • -
-

格式化

-

DecimalFormat 解析

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
符号位置描叙
0数字阿拉伯数字,如果不存在则显示0
#数字阿拉伯数字,如果不存在不显示0
.数字小数分隔符或货币小数分隔符
,数字分组分隔符
E数字分隔科学计数法中的尾数和指数。在前缀或后缀中无需加引号
-数字负号
;子模式边界分隔正数和负数子模式
%子模式边界乘以 100 并显示为百分数
\u2030子模式边界乘以 1000 并显示为千分数
¤(\u00A4)子模式边界货币记号,由货币符号替换。如果两个同时出现,则用国际货币符号替换。如果出现在某个模式中,则使用货币小数分隔符,而不使用小数分隔符
子模式边界用于在前缀或或后缀中为特殊字符加引号,例如 “‘#’#“将 123 格式化为 “#123”。要创建单引号本身,请连续使用两个单引号:”# o’'clock”
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 建立货币格式化引用
NumberFormat currency = NumberFormat.getCurrencyInstance();
// 建立百分比格式化引用
NumberFormat percent = NumberFormat.getPercentInstance();
// 百分比小数点最多3位
percent.setMaximumFractionDigits(3);
BigDecimal loanAmount = new BigDecimal("150.48");
BigDecimal interestRate = new BigDecimal("0.008");
BigDecimal interest = loanAmount.multiply(interestRate);
// 贷款金额: ¥150.48
System.out.println("贷款金额:\t" + currency.format(loanAmount));
// 利率: 0.8%
System.out.println("利率:\t" + percent.format(interestRate));
// 利息: ¥1.20
System.out.println("利息:\t" + currency.format(interest));

//===============================================================
DecimalFormat df = new DecimalFormat();
// 格式化之前的数字
double data = 1234.56789;

// 1.定义要显示的数字的格式(这种方式会四舍五入)
String style = "0.0";
df.applyPattern(style);
// 1-->1234.6
System.out.println("1-->" + df.format(data));

// 2.在格式后添加诸如单位等字符
style = "00000.000 kg";
df.applyPattern(style);
// 2-->01234.568 kg
System.out.println("2-->" + df.format(data));

// 3.模式中的"#"表示如果该位存在字符,则显示字符,如果不存在,则不显示。
style = "##000.000 kg";
df.applyPattern(style);
// 3-->1234.568 kg
System.out.println("3-->" + df.format(data));

// 4.模式中的"-"表示输出为负数,要放在最前面
style = "-000.000";
df.applyPattern(style);
// 4-->-1234.568
System.out.println("4-->" + df.format(data));

// 5.模式中的","在数字中添加逗号,方便读数字
style = "-0,000.0#";
df.applyPattern(style);
// 5-->-1,234.57
System.out.println("5-->" + df.format(data));

// 6.模式中的"E"表示输出为指数,"E"之前的字符串是底数的格式,
// "E"之后的是字符串是指数的格式
style = "0.00E000";
df.applyPattern(style);
// 6-->1.23E003
System.out.println("6-->" + df.format(data));

// 7.模式中的"%"表示乘以100并显示为百分数,要放在最后。
style = "0.00%";
df.applyPattern(style);
// 7-->123456.79%
System.out.println("7-->" + df.format(data));

// 8.模式中的"\u2030"表示乘以1000并显示为千分数,要放在最后。
style = "0.00\u2030";
// 在构造函数中设置数字格式
DecimalFormat df1 = new DecimalFormat(style);
// df.applyPattern(style);
// 8-->1234567.89‰
System.out.println("8-->" + df1.format(data));
-

其他

-

科学计数法问题

-
1
2
3
4
5
BigDecimal b = new BigDecimal("0.0000001");
// 输出结果:1E-7
System.out.println(b.toString());
// 输出结果:0.0000001
System.out.println(b.toPlainString());
-
-

当 BigDecimal的值 小于一定值时(测试时发现:小于等于0.0000001)时,则会被记为科学计数法。可以使用 toPlainString()方法显示原来的值

-
-

去除无效的 0

-
1
2
3
4
5
BigDecimal b = new BigDecimal("0.000000100000000");
// 1E-7
System.out.println(b.stripTrailingZeros().toString());
// 0.0000001
System.out.println(b.stripTrailingZeros().toPlainString());
-
-

stripTrailingZeros() 方法的本质是去除掉多余的0,其返回数据类型是BigDecimal,同样的在使用时需要注意科学技术法的问题

-
-

保留小数位

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
double num = 13.154215;
// 方式一
DecimalFormat df1 = new DecimalFormat("0.00");
String str = df1.format(num);
// 13.15
System.out.println(str);
// 方式二
// #.00 表示两位小数 #.0000四位小数
DecimalFormat df2 =new DecimalFormat("#.00");
String str2 =df2.format(num);
// 13.15
System.out.println(str2);
// 方式三
// %.2f %. 表示 小数点前任意位数 2 表示两位小数 格式后的结果为f 表示浮点型
String result = String.format("%.2f", num);
// 13.15
System.out.println(result);
-

大小比较

-

在比较两个BigDecimal的值是否相等时,要特别注意,使用 equals() 方法不但要求两个BigDecimal的 值相等 ,还要求它们的 scale()相等,因此如果只是比较数值的大小,必须使用 compareTo() 方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于

-
1
2
3
4
5
6
7
8
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("123.45600");
// false,因为scale不同
System.out.println(d1.equals(d2));
// true,因为d2去除尾部0后scale变为2
System.out.println(d1.equals(d2.stripTrailingZeros()));
// 0
System.out.println(d1.compareTo(d2));
-

附录

-
    -
  • Java中浮点类型的精度问题 double float
  • -
  • 廖雪峰 BigInteger
  • -
  • 廖雪峰 BigDecimal
  • -
  • BigDecimal
  • -
  • Java 9中新的货币API
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/10/24/flowable2/index.html b/2019/10/24/flowable2/index.html deleted file mode 100644 index 4d89c776b..000000000 --- a/2019/10/24/flowable2/index.html +++ /dev/null @@ -1,490 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Flowable(二)Modeler-UI 集成 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Flowable(二)Modeler-UI 集成 -

- - -
- - - - -
-
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/10/27/android-string/index.html b/2019/10/27/android-string/index.html deleted file mode 100644 index ff6957876..000000000 --- a/2019/10/27/android-string/index.html +++ /dev/null @@ -1,1185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Android XML字符串 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Android XML字符串 -

- - -
- - - - -

Android在开发过程中,一些特殊字符时无法直接在 strings.xml 文件中写,需要用对应的转义字符代替或者在特殊符号(比如:´" 等待)前添加 \ ,比如一个 TextView 控件中,需要动态替换其中的一些数据,再比如需要调整 TextView 字体的一些HTML样式(比如:粗体,斜体,下划线等),虽然这些都可以用 TextView 去修改,但更简单的方法是设置string提供的属性即可

- -

特殊字符

-
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--
无法直接使用I'm developer
<string name="name_introduce">I'm developer</string>
-->
<!-- 解决方法一 转义字符代替 -->
<string name="name_introduce">I&#039;m developer</string>
<!-- 解决方法二 使用 \ -->
<string name="name_introduces">I\'m developer</string>
</resources>
-

动态替换或拼接

-
    -
  • %n$ms:代表输出的是字符串,n代表是第几个参数,设置m的值可以在输出之前放置空格
  • -
  • %n$md:代表输出的是整数,n代表是第几个参数,设置m的值可以在输出之前放置空格
  • -
  • %n$mf:代表输出的是浮点数,n代表第几个参数,m在浮点类型之前放置几个空格
  • -
-

XML配置

-
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
%1$s 第一个参数,对应Jerry
%2$d 第二个参数,对应36
%3$4.2f 第三个参数,对应 195.1255 ,但保留两位小数,实际显示195.13
-->
<string name="welcome_messages">Hello, %1$s, You have %2$d new messages. total cost %3$4.2f</string>
</resources>
-

Java设置

-
1
2
mTextConent = (TextView) findViewById(R.id.tv_String);
mTextConent.setText(String.format(getString(R.string.welcome_messages), "Jerry", 36, 195.1255));
-

HTML标记

-
    -
  • <b> 表示 粗体 文本。
  • -
  • <i> 表示 斜体 文本。
  • -
  • <u> 表示 下划线 文本。
  • -
-
1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="welcome">Welcome to <b>Android</b>!</string>
<string name="android">Welcome to <i>Android</i>!</string>
<string name="hello">Welcome to <u>Android</u>!</string>
</resources>
-

ASCII对照表

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ASCII码符号ASCII码符号ASCII码符号ASCII码符号
&#064;@&#058;:&#160;空格&#032;空格
&#033;!&#034;"&#035;#&#036;$
&#037;%&#038;&&#039;´&#040;(
&#042; *&#043;+&#044;,&#041;)
&#045;-&#046;.&#047;/&#058;:
&#059;;&#060;<&#061;=&#062;>
&#063;?&#064;@&#091;[&#092;>
&#093;]&#094;^&#095;_&#096;`
&#123;{&#124; |&#125;}&#126;~
&#160;(空格,在xml首字符中不会被忽略)&#161;¡&#162;¢&#163;£
&#164;¤&#165;¥&#166;¦&#167;§
&#168;¨&#169;©&#170;ª&#171;«
&#172;¬&#173;&#174;®&#175;¯
&#176;°&#177;±&#178;²&#179;³
&#180;´&#181;µ&#182;&#183;
&#184;¸&#185;¹&#186;º&#187;»
&#188;¼&#189;½&#190;¾&#191;¿
&#192;À&#193;Á&#194;Â&#195;Ã
&#196;Ä&#197;Å&#198;Æ&#199;Ç
&#200;È&#201;É&#202;Ê&#203;Ë
&#204;Ì&#205;Í&#206;Î&#207;Ï
&#208;Ð&#209;Ñ&#210;Ò&#211;Ó
&#212;Ô&#213;Õ&#214;Ö&#215;×
&#216;Ø&#217;Ù&#218;Ú&#219;Û
&#220;Ü&#221;Ý&#222;Þ&#223;ß
&#224;à&#225;á&#226;â&#227;ã
&#228;ä&#229;å&#230;æ&#231;ç
&#232;è&#233;é&#234;ê&#235;ë
&#236;ì&#237;í&#238;î&#239;ï
&#240;ð&#241;ñ&#242;ò&#243;ó
&#244;ô&#245;õ&#246;ö&#247;÷
&#248;ø&#249;ù&#250;ú&#251;û
&#252;ü&#253;ý&#254;þ&#255;ÿ
&#256;Ā&#257;ā&#258;Ă&#259;ă
&#260;Ą&#261;ą&#262;Ć&#263;ć
&#264;Ĉ&#265;ĉ&#266;Ċ&#267;ċ
&#268;Č&#269;č&#270;Ď&#271;ď
&#272;Đ&#273;đ&#274;Ē&#275;ē
&#276;Ĕ&#277;ĕ&#278;Ė&#279;ė
&#280;Ę&#281;ę&#282;Ě&#283;ě
&#284;Ĝ&#285;ĝ&#286;Ğ&#287;ğ
&#288;Ġ&#289;ġ&#290;Ģ&#291;ģ
&#292;Ĥ&#293;ĥ&#294;Ħ&#295;ħ
&#296;Ĩ&#297;ĩ&#298;Ī&#299;ī
&#300;Ĭ&#301;ĭ&#302;Į&#303;į
&#304;İ&#305;ı&#306;IJ&#307;ij
&#308;Ĵ&#309;ĵ&#310;Ķ&#311;ķ
&#312;ĸ&#313;Ĺ&#314;ĺ&#315;Ļ
&#316;ļ&#317;Ľ&#318;ľ&#319;Ŀ
&#320;ŀ&#321;Ł&#322;ł&#323;Ń
&#324;ń&#325;Ņ&#326;ņ&#327;Ň
&#328;ň&#329;ʼn&#330;Ŋ&#331;ŋ
&#332;Ō&#333;ō&#334;Ŏ&#335;ŏ
&#336;Ő&#337;ő&#338;Œ&#339;œ
&#340;Ŕ&#341;ŕ&#342;Ŗ&#343;ŗ
&#344;Ř&#345;ř&#346;Ś&#347;ś
&#348;Ŝ&#349;ŝ&#350;Ş&#351;ş
&#352;Š&#353;š&#354;Ţ&#355;ţ
&#356;Ť&#357;ť&#358;Ŧ&#359;ŧ
&#360;Ũ&#361;ũ&#362;Ū&#363;ū
&#364;Ŭ&#365;ŭ&#366;Ů&#367;ů
&#368;Ű&#369;ű&#370;Ų&#371;ų
&#372;Ŵ&#373;ŵ&#374;Ŷ&#375;ŷ
&#376;Ÿ&#377;Ź&#378;ź&#379;Ż
&#380;ż&#381;Ž&#382;ž
-

附录

-
    -
  • 字符串资源
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/11/01/mysql1/index.html b/2019/11/01/mysql1/index.html deleted file mode 100644 index cc2cbd955..000000000 --- a/2019/11/01/mysql1/index.html +++ /dev/null @@ -1,594 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -MySQL 必备技能 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- MySQL 必备技能 -

- - -
- - - - -

- -

SQL 语句

-

SQL全称(Structured Query Language):是一种特定目的编程语言,用于管理关系数据库管理系统(RDBMS),或在关系流数据管理系统(RDSMS)中进行流处理
-也就是一种数据库查询和程序设计语言,用于存取数据以及查询和管理关系型数据库

-

SQL 规则

-
    -
  1. SQL 语句可以单行或多行书写,以分号 ; 结尾
  2. -
  3. 可以使用空格和缩进来增强语句可读性
  4. -
  5. MySQL数据库的 SQL 语句不区分大小写,关键字建议大写
  6. -
-

SQL 分类

-

SQL_Commands

-

DDL

-

Data Definition Language(DDL):数据定义语言,用来创建数据库中的表,索引,视图,存储过程,触发器等。

-
操作数据库
-
    -
  • CREATE:创建
    1
    2
    3
    4
    5
    6
    # 创建数据库
    CREATE DATABASE 数据库名称;
    # 创建数据库,判断是否存在,不存在则创建
    CREATE DATABASE IF NOT EXISTS 数据库名称;
    # 创建数据库并指定其字符集
    CREATE DATABASE 数据库名称 CHARACTER SET 字符集;
    -
  • -
  • ALERT:修改
    1
    2
    # 修改数据库的字符集
    ALTER DATABASE 数据库名称 CHARACTER SET 字符集名称;
    -
  • -
  • DROP:删除
    1
    2
    3
    4
    # 删除数据库
    DROP DATABASE 数据库名称;
    # 判断数据库存在,存在则删除
    DROP DATABASE IF EXISTS 数据库名称;
    -
  • -
  • 查询
    1
    2
    3
    4
    # 查询所有数据库的名称
    SHOW DATABASES;
    # 查询某个数据库的创建语句
    SHOW CREATE DATABASE 数据库名称;
    -
  • -
-
操作表
-
    -
  • CREATE:创建
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 语法
    CREATE TABLE tableName(
    列名1 数据类型1,
    列名2 数据类型2,
    ....
    列名n 数据类型n,
    [添加约束...]
    );
    # 示例
    -
  • -
  • ALERT:修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 修改表名
    ALTER TABLE 表名 RENAME TO 新表名;
    # 修改表的字符集
    ALTER TABLE 表名 CHARACTER SET 字符集名称;
    # 添加一列
    ALTER TABLE 表名 ADD 列名 数据类型;
    # 修改列名 类型
    ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
    # 只修改数据类型
    ALTER TABLE 表名 MODIFY 列名 新数据类型;
    # 删除列
    ALTER TABLE 表名 DROP 列名;
    -
  • -
  • DROP:删除
    1
    2
    3
    4
    # 删除表
    DROP TABLE 表名;
    # 判断表是否存在,存在则删除
    DROP TABLE IF EXISTS 表名;
    -
  • -
-

TRUNCATE 和 DELETE 区别

-
    -
  1. TRUNCATE TABLE 表名 语句在功能上与不带 WHERE 子句的 DELETE 语句相同;二者均删除表中的全部数据,但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少
  2. -
  3. DELETE 语句每次删除一行,并在事务日志中为所删除的每一行记录。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且旨在事务日志中记录页的释放
  4. -
  5. TRUNCATE TABLE 删除表中的所有行,但表结构及其列,约束,索引等保存不变,且会重置表的计数(通常我们作为表的主键);DELETE TABLE 不会重置计数;如果要删除表定义及其数据,使用 DROP TABLEE 语句
  6. -
-
-

DML

-

Data Manipulation Language(DML):数据操作语言,用来改变数据库数据

-
    -
  • -

    INSERT

    -
    1
    2
    3
    4
    5
    # 插入
    INSERT INTO 表名(字段列表) VALUES(值列表)
    # 拷贝表
    INSERT INTO 新表名 SELECT * FROM 表名;
    CREATE TABLE 新表名 LIKE 表名;
    -
  • -
  • -

    UPDATE

    -
    1
    UPDATE 表名 SET 字段1=1,字段n=值n [WHERE 条件] [ORDER BY 字段名 ASC|DESC] [LIMIT];
    -
  • -
  • -

    DELETE

    -
    1
    DELETE FROM 表名 [WHERE 条件] [ORDER BY 字段名 ASC|DESC] [LIMIT];
    -
  • -
-

DQL

-

Data Query Language(DDL):数据查询语言,用于建立,修改,删除数据库中的各种对象

-
1
2
3
4
5
6
7
8
SELECT
column_1,column_2,...
FROM table_1
[INNER | LEFT |RIGHT] JOIN table_2 ON CONDITIONS
WHERE conditions
GROUP BY column_1
HAVING group_conditions
ORDER BY column limit offset,length
-

DCL

-

Data Control Language(DCL):数据控制语言

-
    -
  • GRANT
  • -
  • REVOKE
  • -
-

TCL

-

Transaction Control Language(TCL): 事务控制语言,用于维护数据的一致性

-

索引

-

字符集

-

常用命令

-

锁表处理

-

方法一

-
    -
  1. 查看是否锁表
    1
    show OPEN TABLES where In_use > 0;
    -
  2. -
  3. 查看进程,查找被锁表的进程ID
    1
    show processlist;
    -
  4. -
  5. kill 锁表的进程 ID
    1
    kill id;
    -
  6. -
-

方法二

-
    -
  1. 查看当前数据库的锁表情况
    1
    SELECT * FROM information_schema.INNODB_TRX;
    -
  2. -
  3. 杀掉查询结果中锁表的trx_mysql_thread_id
    1
    kill trx_mysql_thread_id
    -
  4. -
-

MySQL 用户分配

-

mysql-account

-

参考

-
    -
  • 再见乱码:5分钟读懂MySQL字符集设置
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/11/19/deploy-springboot/index.html b/2019/11/19/deploy-springboot/index.html deleted file mode 100644 index 59dc1e301..000000000 --- a/2019/11/19/deploy-springboot/index.html +++ /dev/null @@ -1,603 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -IDEA 之 SpringBoot 应用部署 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- IDEA 之 SpringBoot 应用部署 -

- - -
- - - - -

服务端由原来 混合式(Java+JSP)的方式演进成专注于提供服务 API(前后端分离)的方式,开发的明确分工,使的各自开发人员在各自领域的垂直技能的加强,以满足业务的快速迭代,因此也就在这两个方式中,项目的构建方式也有了一定的变化,混合模式中常编译为 war 包,而在前后端分离模式中常编译为 jar 包,这两种文件格式虽然都是一种压缩文件的格式,但实质还是有一些区别,那首先让我们来了解这两种文件它们之间的区别

- -

jar 文件与 war 文件的区别

-
    -
  1. war 文件常应用在 混合式 的项目中,war 文件包含Java 相关的项目文件、部署文件,还包含一些前端页面等引用的相关资源文件;jar 文件常应用在 前后端分离 的项目中,jar 文件主要包含Java 相关的项目文件、部署文件
  2. -
  3. war 文件中不包含 Tomcat相关文件,必须运行在 Tomcat 容器中;jar 文件中内置了 Tomcat 文件,可直接运行
  4. -
  5. war 文件通常使用 SSM 架构;jar 文件通常使用 SpringBoot/SpringCloud 架构
  6. -
  7. 无论是 jar 还是 war 都能够使用嵌套容器,java -jar来独立运行
  8. -
  9. 只有 war 才能部署到外部容器中;SpringBoot支持多种模板引擎,但JSP 只能在 war 中使用
  10. -
-

准备

-
    -
  • System:macOS
  • -
  • Java:JDK 1.8+
  • -
  • 编辑器:IDEA
  • -
  • 包管理:maven/gradle
  • -
  • 服务器连接:iTerm2
  • -
  • 项目示例: -
      -
    • rc-cluster-springboot,这是一个 gradle 管理的 SpringBoot 项目
    • -
    • rc-ssm,这是一个 maven 管理的 SSM 项目
    • -
    -
  • -
-
-

Windows 连接服务器工具可使用 Xshell 等代替

-
-

编译

-

编译 war

-
1
2
<!-- pom.xml 文件中要指明构建文件的类型 -->
<packaging>war</packaging>
-
1
2
# clean依赖并编译成 package,也可以执行 mvn clean package -DskipTests
mvn clean package -Dmaven.test.skip=true
-

deploy-maven-war

-

编译 jar(SpringBoot)

-

maven

-
1
2
<!-- pom.xml 文件中要指明构建文件的类型 -->
<packaging>jar</packaging>
-
GUI 操作
-

deploy-maven-jar

-
命令行 操作
-

定位到需要构建的模块下,执行如下命令

-
1
2
# clean依赖并编译成 package,也可以执行 mvn clean package -DskipTests
mvn clean package -Dmaven.test.skip=true
-

maven-package-jar

-

gradle

-
GUI 操作
-

deploy-gradle

-
命令行 操作
-

在需要编译的项目路径下,这里编译子模块springboot-start,前提需要在系统的环境变量中配置好 gradle,macOS 可参考MacBook Pro 初始化文章

-
1
2
3
4
# linux or macOS
gradle build
# Windows
./gradle build
-

deploy-gradle-build

-

部署

-

部署步骤:

-
    -
  1. 备份服务器对应服务,并停止服务运行(如果是第一次部署该服务,可省略)
  2. -
  3. 上传应用 jar/war 文件
  4. -
  5. 启动上传应用
  6. -
-
1
2
3
4
5
# 拷贝本地文件到指定服务器的指定目录
# root:服务器用户名
# ip:服务器地址
# :/data/app:拷贝文件到服务器的/data/app 路径下
scp ~/Desktop/start-1.0-SNAPSHOT.jar root@ip:/data/app
-

部署 war

-
-

由于 war 包并没有内置 Tomcat 等运行的容器,因此需要你的服务器已经安装了 Tomcat 等服务

-
-

部署 jar

-

iTerm2 部署

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ssh 连接到服务器后,启动应用,如果有需要请先停止已在运行的程序
# 查看程序的进程
ps -ef|grep 'java -jar'
# UID PID PPID C STIME TTY TIME CMD
# root 6760 7103 0 19:59 pts/3 00:00:00 java -jar /data/app/start-1.0-SNAPSHOT.jar

# 终止程序进程
kill -9 pid

# nohup:后台运行程序,也就说当控制台终止,并不会停止启动的服务
# spring.log:当前目录下输出日志记录的文件名
nohup java -jar start-1.0-SNAPSHOT.jar > spring.log 2>&1 &

# 滚动查看日志输出
tail -f spring.log

# 如果不需要查看输入出的日志,运行如下脚本
nohup java -jar start-1.0-SNAPSHOT.jar &
-

tail 命令

-
    -
  • 命令格式:tail[必要参数][选择参数][文件]
  • -
  • 功能描述:用于显示指定文件末尾内容,不指定文件时,作为输入信息进行处理;查看日志文
  • -
  • 示例:tail -f spring.log
  • -
  • 命令参数 -
      -
    • -f 循环读取
    • -
    • -q 不显示处理信息
    • -
    • -v 显示详细的处理信息
    • -
    • -c<数目> 显示的字节数
    • -
    • -n<行数> 显示行数
    • -
    • –pid=PID 与-f合用,表示在进程ID,PID死掉之后结束.
    • -
    • -q, –quiet, –silent 从不输出给出文件名的首部
    • -
    • -s, –sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒
    • -
    -
  • -
-

Alibaba Cloud Toolkit

-

alibaba-cloud-toolkit

-

视频教程:https://cloud.video.taobao.com/play/u/650480598/p/1/e/6/t/1/216889058961.mp4

-

Jenkins

-

关于 Jenkins 的部署,会单独出一篇相关的部署介绍,以及 Jenkins 的相关知识介绍,请移步使用 Jenkins 项目部署

-

问题

-

gradle 编译失败

-

deploy-gradle-build-error

-

原因:这里的提示spring-boot-2.1.6.RELEASE.jar文件无法正常下载
-解决办法:切换你的网络,或者修改 gradle 仓库镜像地址

-

缓存文件未下载完全

-

-

原因:文件未下载完全,编译时使用了缓存文件
-解决办法:删除~/.gradle/caches/modules-2/files-2.1路径下无法编译通过的包,这里是spring-boot-gradle-plugin

-

附录

-
    -
  • SpringBoot入门系列(四)—Spring Boot 项目打包运行
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/11/20/netty/index.html b/2019/11/20/netty/index.html deleted file mode 100644 index 760249102..000000000 --- a/2019/11/20/netty/index.html +++ /dev/null @@ -1,586 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Netty初体验(一) | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Netty初体验(一) -

- - -
- - - - -

Netty 是国内外各大互联网公司的必备网络应用框架,Netty 主要处理与网络相关的一些应用。由于 Netty 设计的巧妙的实现方式,以及对协议很好的实现,使的 Netty 可以在各种应用场景下广泛的应用,无论是传统基于HTTP协议的访问方式,还是更底层基于socket的访问方式,以及支持HTML5规范中的websocket的长连接特性,都提供了比较好的支持

- -

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients(Netty 是一个异步的,事件驱动的网络应用框架,是可维护的,高性能的,协议化的服务端和客户端快速开发方式)

-

Netty 是一个非阻塞(NIO)客户端服务端框架,它可以快速的进行网络应用开发,例如:基于协议的客户端和服务端。它极大的简化并且支持流式的网络程序,例如:基于TCP和UDP的socket服务
-'快捷方便’并不意味着最终的应用程序会受到可维护性或性能问题的影响。Netty经过精心设计,具有实施许多协议所获得的经验,如FTP,SMTP,HTTP以及各种基于二进制和文本的遗留协议。因此,Netty 成功地找到了一种在不妥协的情况下实现易于开发,性能,稳定性和灵活性的方法

-

特点

-
    -
  • 适用于各种传输类型统一API - 阻塞和非阻塞 socket
  • -
  • 基于灵活且可扩展的事件模型,可以清晰地关注分离(separation of concerns)
  • -
  • 高度可定制的线程模型 - 单线程,一个或多个线程池,如:Staged Event Driven Architecture(SEDA,阶段型事件驱动架构,将一个请求分成若干个阶段,每个阶段可以根据自身情况不用数量的线程来分别进行处理,阶段与阶段之间是通过事件驱动的这种异步通讯模式来进行沟通及通信)
  • -
  • 真正的无连接数据报套socket支持(since 3.1)
  • -
-

性能

-
    -
  • 更高的吞吐量,更低的延迟
  • -
  • 减少资源消耗
  • -
  • 不必要的内存复制降到最低
  • -
-

安全

-
    -
  • 完整的SSL / TLS 和 StartTLS 支持
  • -
-

Netty 使用场景

-
    -
  1. 作为HTTP的服务器,类似与Jetty,Tomcat这种Servlet容器,只是Netty在充当HTTP的服务器时,它采用的编程模型并不是基于Servlet的规范,原因是Netty并没有实现Servlet的接口,Servlet的实现,Netty有自己的实现方式
  2. -
  3. 作为RPC通讯的框架,通讯的协议(可自定义),通讯的库,实现远程过程的调用,基于Socket方式(广泛使用)
  4. -
  5. 作为长连接的服务器,基于WebSocket,实现客户端和服务端之间的长连接通信
  6. -
-

环境说明

-
    -
  • System:macOS
  • -
  • Java:JDK1.8+
  • -
  • Netty:4.1.25.Final
  • -
-

HTTP

-

Netty 基于Servlet的规范,是一种特定的方式(更底层),因此 Netty 更专注于底层的性能等方面,所以在应用层开发时,是由开发人员自行去组装对请求,对 请求路由 处理等

-
-

示例:HTTP

-
-

Socket

-

Socket 是计算机网络中用于在节点内发送或接收数据的内部端点。具体来说,它是网络软件 (协议栈) 中这个端点的一种表示,包含通信协议、目标地址、状态等,是系统资源的一种形式

-
-

示例:Socket

-
-

Websocket

-

WebSocket 是HTML5 规范的一部分,也是基于 HTTP 协议之上的一种协议,WebSocket主要是解决 HTTP 上存在的一些问题;

-
    -
  1. HTTP 一种无状态(同一客户端发出的第一次请求接收到响应后,客户端发送第二次请求,这两次请求之间没有任何关联)的协议,HTTP 无法追踪某一请求来自哪一个客户端,客户端之前在服务器上存在一些信息(常见的解决方式:cookie,session)
  2. -
  3. HTTP 是基于请求响应模式的协议,请求的发起方一定是客户端,服务器将响应返回给客户端后连接就断掉了(HTTP 1.0),在 1.0 的基础上连接可以短时间的保持,一种 keep-alive 机制(HTTP 1.1)
  4. -
-

通常我们所使用的长连接技术

-
    -
  • 早期采用轮询的方式保持与服务器的连接
  • -
  • 目前通常采用 Websocket 的连接方式保持与服务器的连接 -
      -
    • 客户端(浏览器)与服务器建立连接后,没有其他因素干扰,连接是不会断,一直存在,客户端与服务器双方是对等的,不再区分谁是客户端,谁是服务端,客户端可以发送数据给服务端,服务端也可以发送数据给客户端,在真正意义上实现了服务端的推技术
    • -
    • 长连接在建立初期会发送带有 header 头信息的网络请求,在连接建立后,在长连接之上只需要发送需要传递的数据(真正的数据)即可
    • -
    • Websocket 是基于 HTTP 协议
    • -
    • Websocket 也可以用于非浏览器的场景,只要你的库支持 Websocket 即可
    • -
    -
  • -
-
-

示例:

-
    -
  • Websocket Server
  • -
  • Websocket Client
  • -
-
-

Heartbeat

-

对于服务器上的集群服务(zookeeper 或者其他的应用服务),或者是客户端与服务端之间的长连接,需要一种机制来检测客户端还是 alive,这种机制就是 heartbeat

-
    -
  • 对于服务器上的这些服务与服务之间,节点与节点之间的通信(无一例外都使用 TCP 连接通信),节点之间的通信如何保证(A 节点感知到 B 或者其他节点是未宕机),此时就需要心跳来检测对应的服务或节点还是正常的
  • -
  • 对于客户端与服务器之间由于网络问题,或者客户端开启飞行模式,或者关机等状态,服务端是无法感知,因此也需要借助心跳来检测客户端是否关机或开启了飞行模式
  • -
-
-

示例:Heartbeat

-
-

总结

-

Netty 程序编写步骤

-
    -
  1. 定义好父子的(bossGroup:获取链接,workerGroup:真正来处理链接)线程组(EventLoopGroup),服务器启动时关联一个处理器处理器类似Initializer这样的处理器
  2. -
  3. Initializer定义好自定义的或Netty本身提供的ChannelHandler通道处理器,在initChannel中自定义添加若干个处理器
  4. -
  5. 实现自定义处理器ChannelHandler中特定的回调方法
  6. -
-

Netty 程序测试

-
    -
  1. 启动 Server 服务
  2. -
  3. 访问启动的服务,使用 curl命令 或者使用浏览器访问进行访问
  4. -
-

netty-test

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/11/29/netty-protobuf/index.html b/2019/11/29/netty-protobuf/index.html deleted file mode 100644 index 7ad5509a9..000000000 --- a/2019/11/29/netty-protobuf/index.html +++ /dev/null @@ -1,587 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Netty(二)之 Protobuf | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Netty(二)之 Protobuf -

- - -
- - - - -

Netty 框架中已经默认支持了 Protobuf 格式的数据传输,因此我们本节就来学习 Protobuf,Protobuf 主要用于进行 RPC 数据传输(它是一种自定义协议,这种协议能更好,更小体积,对数据编解码【序列号和反序列化的过程】),在学习 Protobuf 之前我们先了解两个概念 RMI 和 RPC

- -

RMI:Remote Method Invocation,用于跨机器方法调用,只针对于 Java(要求调用者和被调用者都必须是 Java 程序)

-
    -
  • client:stub(装)
  • -
  • server:skeleton(骨架)
    -client 与 server 底层通过 socket 数据传输
  • -
-

RPC:Remote Procedure Call,远程过程调用,原理和 RMI 一致,优势在于跨语言支持

-

那对于 RMI 和 RPC 编写的具体步骤如下:

-
    -
  1. 定义接口说明文件(IDL:Interface Description Language ):描述对象(结构体),对象成员,接口方法等一系列信息
  2. -
  3. 通过 RPC 框架所提供的编译器,将说明文件编译成具体语言文件
  4. -
  5. 在客户端与服务器端分别引入 RPC 编译器所生产的文件,即可享调用本地方法一样调用远程方法
  6. -
-

序列化与反序列化

-

序列化与反序列化也叫做,编码与解码

-

序列化:将对象转换成字节,这个过程是encode
-反序列化:将字节翻译成对象,这个过程是decode

-

Protobuf

-
    -
  • 官方网站:Protocol Buffers
  • -
  • 官方指南:Guide
  • -
  • 官方说明:Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.(Protocol buffers 是一种语言中立,平台中立,可扩展的一种机制用于序列化结构化的数据)
  • -
-

Protobuf 编译环境搭建

-
    -
  1. 下载对应系统的编译器,格式如 protoc-$VERSION-$PLATFORM.zip,这里下载的是 protoc-3.11.0-osx-x86_64.zip
  2. -
  3. 为了使用方便,我们需要将 protoc 解压的路径添加到环境变量中
  4. -
  5. 在终端中使用 protoc -h 命令验证 protoc 变量是否配置正确
  6. -
-

Protobuf 特定语言

-

这是一步可选步骤,根据自身需要,选择需要的语言编译文件,这里下载的是 protobuf-java-3.11.0.zip,用于学习了解 protoc 对 Java 编译的支持原理等

-

Protobuf 使用

-

在官方 README 介绍中,请查看Protobuf Runtime Installation说明,这里介绍了在使用不同语言时需要安装的一些依赖,比如这里查看 Java,在需要使用的项目中引入相关的依赖

-

简单使用

-
    -
  1. 编写.proto文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    syntax = 'proto2';

    package org.incoder.protoc;

    option optimize_for = SPEED;
    option java_package = "org.incoder.protobuf";
    option java_outer_classname = "HelloProtobuf";


    message World {
    required string name = 1;
    optional string address = 3;
    }
    -
  2. -
  3. 执行编译命令,protoc --java_out=$DST_DIR $SRC_DIR/FILE_NAME.proto
  4. -
  5. 编写简单的测试,明白 RPC 的过程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public static void main(String[] args) throws InvalidProtocolBufferException {
    ///////////////////////////////////////////////////////////////////////////
    // 把下面的这个过程等同到 RPC 的过程
    ///////////////////////////////////////////////////////////////////////////

    // A机器上构建了World对象
    HelloProtobuf.World world = HelloProtobuf.World.newBuilder()
    .setName("China")
    .setAddress("处于地球东半球")
    .build();

    // A 机器构建的对象转换成字节数组
    // 字节数组通过网络传输(Netty 等方式) A 机器传输到 B 机器
    byte[] world2ByteArray = world.toByteArray();

    // B 机器上把字节数转换成对象(取决于在 B 机器上的使用语言),并把数据打印出来
    HelloProtobuf.World worlds = HelloProtobuf.World.parseFrom(world2ByteArray);
    System.out.println(worlds);
    }
    -
  6. -
-

整个过程如下截图
-

-

在 Netty 中的应用(单消息)

-

和之前Netty初体验(一)中编写步骤一样,这里只是对Initializer 中使用Netty 提供相关 Protobuf 的工具类

-
    -
  • ProtobufDecoder:将收到的 ByteBuf 解码为 Google Protocol Buffers 和 MessageLite(),请注意,如果使用基于流的传输方式(比如:TCP/IP),则此解码器必须与适当的 ByteToMessageDecoder(如:ProtobufVarint32FrameDecoder 或者 ProtobufVarint32LengthFieldPrepender)
  • -
  • ProtobufDecoderNano:将接收到的 ByteBuf解码为 Google Protocol Buffers MessageNano,请注意,如果使用的是基于流的传输方式(如:TCP/IP),则此解码器必须与适当的 ByteToMessageDecoder(如果:LengthFieldBasedFrameDecoder)一起使用
  • -
  • ProtobufEncoder:将请求的 Google Protocol Buffers 和 MessageLite 编码为 ByteBuf
  • -
  • ProtobufEncoderNano:将请求的 Google Protocol Buffers MessageNano 编码为 ByteBuf
  • -
  • ProtobufVarint32FrameDecoder:解码器按消息中 Google Protocol Buffers 基于 128 Varints 整数长度字段的值动态拆分接收到的 ByteBuf
    1
    2
    3
    4
    5
    6
    For example:
    BEFORE DECODE (302 bytes) AFTER DECODE (300 bytes)
    +--------+---------------+ +---------------+
    | Length | Protobuf Data |----->| Protobuf Data |
    | 0xAC02 | (300 bytes) | | (300 bytes) |
    +--------+---------------+ +---------------+
    -
  • -
  • ProtobufVarint32LengthFieldPrepender:一种编码器,可在 Google Protocol Buffers Base 128 Varints 之前添加
    1
    2
    3
    4
    5
    BEFORE ENCODE (300 bytes)       AFTER ENCODE (302 bytes)
    +---------------+ +--------+---------------+
    | Protobuf Data |-------------->| Length | Protobuf Data |
    | (300 bytes) | | 0xAC02 | (300 bytes) |
    +---------------+ +--------+---------------+
    -
  • -
-
SingleClient
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SingleClient {

public static void main(String[] args) throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new SingleClientInitializer());

ChannelFuture channelFuture = bootstrap.connect("localhost", 5555).sync();
channelFuture.channel().closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
-
SingleClientInitializer
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SingleClientInitializer extends ChannelInitializer<SocketChannel> {

@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(NettyDataInfo.Person.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());

pipeline.addLast(new SingleClientHandler());
}
}
-
SingleClientHandler
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SingleClientHandler extends SimpleChannelInboundHandler<NettyDataInfo.Person> {

@Override
protected void channelRead0(ChannelHandlerContext ctx, NettyDataInfo.Person msg) throws Exception {

}

/**
* 客户端建立连接后发送消息给服务端
*
* @param ctx ctx
* @throws Exception exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
NettyDataInfo.Person person = NettyDataInfo.Person.newBuilder()
.setName("netty")
.setAge(20)
.setAddress("https://netty.io")
.build();

// 发送消息给服务器
ctx.channel().writeAndFlush(person);
}
}
-
SingleServer
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SingleServer {

public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();

try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new SingleServerInitializer());

ChannelFuture channelFuture = bootstrap.bind(5555).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
}
}
}
-
SingleServerInitializer
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SingleServerInitializer extends ChannelInitializer<SocketChannel> {

@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(NettyDataInfo.Person.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());

pipeline.addLast(new SingleServerHandler());
}
}

-
SingleServerHandler
-
1
2
3
4
5
6
7
8
9
10
public class SingleServerHandler extends SimpleChannelInboundHandler<NettyDataInfo.Person> {

@Override
protected void channelRead0(ChannelHandlerContext ctx, NettyDataInfo.Person msg) throws Exception {
// 打印客户端连接后发送的消息
System.out.println(msg.getName());
System.out.println(msg.getAge());
System.out.println(msg.getAddress());
}
}
-

在 Netty 中的应用(多消息)

-

由于通过 Netty(底层是 socket)使客户端与服务端建立连接,使用 Google Protocol Buffers 协议进行数据通信,而 ProtobufDecoder(MessageLite prototype) 需要指定具体的实例,因此想要进行多消息类型数据通信,可以有两种方式

-
    -
  1. 自定义通信协议
  2. -
  3. 在定义 IDL 时,将所有类型的数据进行定义,最终生成一个包含了通信所需的所有类型的顶层 Message
  4. -
-

方式二具体代码可参考:multiple

-

问题

-

环境搭建问题

-

在配置好环境变量后,执行 protoc -h 命令提示 “protoc” cannot be opened because the developer cannot be verified.
-

-
    -
  • 原因:在 macOS 10.15 版本上未授权访问
  • -
  • 解决:在系统设置中,进行授权,操作如下 Settings -> Security & Privacy -> General
    -
  • -
  • 验证:在终端中执行 protoc -h 命令
    -
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2019/12/01/netty-thrift/index.html b/2019/12/01/netty-thrift/index.html deleted file mode 100644 index eb9c17c8a..000000000 --- a/2019/12/01/netty-thrift/index.html +++ /dev/null @@ -1,567 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Netty(三)之 Thrift | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Netty(三)之 Thrift -

- - -
- - - - -

The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.

-

Apache Thrift软件框架,用于可扩展的跨语言服务开发,它包含软件栈和一个代码生成器用于构建服务,这个服务可以高效并且无缝的在 C++,Java,Python,PHP,Ruby,Erlang,Perl,Haskell,C#,Cocoa,Node.js,Smalltalk,OCaml 和 Delphi 等其他语言间协作

- -

Apache ThriftGoogle Protocal Buffers 都是一种可以用于在 Netty 之上的一种数据格式,Thrift 可应用的语言比 Protocal Buffers 多,并且 Thrift 除了用于传递数据的定义,底层还提供了传输层,因此可以单独的去使用,而不必强制运行在 Netty 载体之上

-

Thrift

-

Thrift数据类型

-

Thrift不支持无符号类型,因为很多编程语言不存在无符号类型,比如Java

-
    -
  • byte:有符号字节
  • -
  • i16:16位有符号整数
  • -
  • i32:32位有符号整数
  • -
  • i64:64位有符号整数
  • -
  • double:64位浮点数
  • -
  • string:字符串
  • -
  • bool:布尔值
  • -
-

Thrift容器类型

-
    -
  • list:一系列由T类型的数据组成有序列表,元素可以重复 -
    -

    集合中的元素可以是除了service之外的任何类型,包括exception

    -
    -
  • -
  • set:一系列由T类型的数据组成的无序列表,元素不可以重复
  • -
  • map:一个字典结构,key 为 K类型,value为 V类型,相当于Java中的 HashMap
  • -
-

Thrift支持的三类组件

-

struct

-

结构体,编译生成完成后,对应的是我们的类,就像 C 语言一样,thrift 支持 struct 类型,目的就是将一些数据聚合在一起,方便传输管理。struct 的定义形式如下:

-
1
2
3
4
5
struct People{
1:string name;
2:i32 age;
3:string gender;
}
-

枚举,枚举的定义形式和 Java 的 Enum 定义类似

-
1
2
3
4
enum Gender{
MALE,
FEMALE
}
-

exception

-

异常,客户端与服务端之间通信用到的接口可能抛出的异常,thrift 支持自定义 exception,规则与 struct 一样

-
1
2
3
4
exception RequestException{
1:i32 code;
2:string reason;
}
-

service

-

服务,客户端与服务端之间通信用到的接口,thrift 定义服务相当于 Java 中创建 interface 一样,创建的 service 经过代码生成命令之后就会生成客户端和服务端的框架代码

-
1
2
3
4
service HelloWorldService{
// service 中定义的函数,相当于 Java interface 中定义的方法
string doAction(1:string name, 2:i32 age);
}
-

类型定义

-

thrift 支持类似 C++ 一样的 typedef 定义,在定义完别名后,在后面的 IDL 文件中就可以使用别名进行编写

-
1
2
3
4
// 把 i32 别名成 int
typedef i32 int
// 把 i64 别名成 long
typedef i64 long
-

常量

-

thrift 也支持常量定义,使用 const 关键字

-
1
2
const i32 MAX_RETRIES_TIME = 10
const string MY_WEBSITE = "https://incoder.org"
-

命名空间

-

thrift 的命名空间相当于 Java 中的 package 的意思,主要目的是组织代码。thrift 使用关键字 namespace 定义命名空间

-
1
2
// 格式:namespace 语言名 路径
namespace java org.incoder.thrift
-

文件包含

-

thrift 也支持文件包含,相当于 C/C++ 中的 include,Java 中的 import,使用关键字 include 定义

-
1
include "global.thrift"
-

可选与必选

-

thrift 提供两个关键字requiredoptional,分别用于表示对应的字段是必填还是可选,主要根据你的业务来选择,推荐使用 optional

-
1
2
1:required string name;
2:optional i32 age;
-

Thrift工作原理

-

数据之间的传输使用socket(多种语言均支持),数据载以特定的格式(String等)发送,接收方进行语言解析。通过定义 Thrift 文件,由 Thrift 文件(IDL)生成双方语言的接口,model,在生成的model及接口中会有解析码,编码的代码

-

Thrift 实践

-

下载Thrift

-

这一步可以直接通过 maven 或者 gradle 的方式集成 Thrift 包到所需要的项目包管理中即可

-
1
2
3
4
5
6
7
8
9
# maven
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.13.0</version>
</dependency>

# gradle
compile 'org.apache.thrift:libthrift:0.13.0'
-

编译器安装

-

对于 macOS 可使用官方提供的方式去安装,也可以借助于 macOS 上,优秀的包管理工具 Homebrew 来进行安装,我这里就直接使用 Homebrew 进行安装,其他系统可参考官方文档

-
1
brew install thrift
-

编写.thrift文件

-

编写.thrift 文件的指南,可以参考官网文档,编写完文件,使用 thrift 编译器提供的命令生成相关的代码

-
1
thrift --gen <language> <Thrift filename>
-

示例

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 定义命名空间
namespace java org.incoder.thrift.java
namespace py org.incoder.thrift.py

// 定义别名
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String

// 定义 struct
struct Person{
1: optional String username,
2: optional int age,
3: optional boolean married
}

// 定义 exception
exception DataException{
1: optional String message,
2: optional String callStack,
3: optional String date
}

// 定义 service
service PersonService{
Person getPersonByUsername(1: required String username) throws (1: DataException dataException),

void savePerson(1: required Person person) throws (1: DataException dataException)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
try:
person_handler = PersonHandler()
processor = PersonService.Processor(person_handler)

serverSocket = TSocket.TServerSocket(port=9090)
transportFactory = TTransport.TFramedTransportFactory()
protocolFactory = TCompactProtocol.TCompactProtocolFactory()

server = TServer.TSimpleServer(processor, serverSocket, transportFactory, protocolFactory)
server.serve()

except Thrift.TException as tx:
print(tx.message)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
try:
tSocket = TSocket.TSocket('localhost', 9090)
tSocket.setTimeout(600)

transport = TTransport.TFramedTransport(tSocket)
protocol = TCompactProtocol.TCompactProtocol(transport)
client = PersonService.Client(protocol)

transport.open()
person = client.getPersonByUsername("张三")
print("username:" + person.username)
print("age:" + str(person.age))
print("married:" + str(person.married))

print("------------------------")
newPerson = ttypes.Person()
newPerson.username = "李四"
newPerson.age = 30
newPerson.married = True

client.savePerson(newPerson)
transport.close()
except Thrift.TException as tx:
print(tx.message)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) throws Exception {
TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(9090);
THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4);
PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());

arg.protocolFactory(new TCompactProtocol.Factory());
arg.transportFactory(new TFramedTransport.Factory());
arg.processorFactory(new TProcessorFactory(processor));

TServer server = new THsHaServer(arg);
System.out.println("Thrift service Started!");
// 开启死循环
server.serve();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void main(String[] args) {
TTransport transport = new TFramedTransport(new TSocket("localhost", 9090), 600);
TProtocol protocol = new TCompactProtocol(transport);
PersonService.Client client = new PersonService.Client(protocol);

try {
transport.open();
// 调用定义通过用户名获取用户信息的接口方法 getPersonByUsername
Person person = client.getPersonByUsername("张三");
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());

System.out.println("-----------------------------");

// 调用定义保存用户信息的方法
Person per = new Person();
per.setUsername("李四");
per.setAge(30);
per.setMarried(true);
client.savePerson(per);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
transport.close();
}
}
-
-

相关源码rc-cluster-netty

-
-

其他

-

本地 Thrift 的 Python 环境,需要下载官方的文件,进行安装

-
1
2
3
4
5
6
7
8
9
10
# 方式一:
pip3 install thrift
# 方式二,下载官方包,进行安装
# 1. 下载文件
curl https://mirrors.tuna.tsinghua.edu.cn/apache/thrift/0.13.0/thrift-0.13.0.tar.gz
# 2. 解压文件
tar -zvxf thrift-0.13.0.tar.gz
# 3. 安装thrift
cd thrift-0.13.0/lib/py/
sudo python setup.py install
-
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/01/22/summary-2019/index.html b/2020/01/22/summary-2019/index.html deleted file mode 100644 index 7638d85d6..000000000 --- a/2020/01/22/summary-2019/index.html +++ /dev/null @@ -1,503 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -复盘 2019 —— 安全上车 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 复盘 2019 —— 安全上车 -

- - -
- - - - -
- -
-
- - -
-
-
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/02/02/springboot5/index.html b/2020/02/02/springboot5/index.html deleted file mode 100644 index e9644b0cf..000000000 --- a/2020/02/02/springboot5/index.html +++ /dev/null @@ -1,515 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(五)多环境配置 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(五)多环境配置 -

- - -
- - - - -

在 SpringBoot 项目中,常用的包管理分别为 maven 和 gradle,在不同包管理下我们如何实现多环境的项目配置,这是实际项目开发过程汇总必备的一项技能,可以大大提高我们开发部署效率,同时也避免了人为的频繁改动配置造成的问题等,有些人可能会问了,maven 不是用的好好的嘛,干嘛还要用 gradle,首先我们可以看现在主流开源项目在提供引入方式时都是有提供了 gradle 依赖方式,以及 gradle 支持编写脚本,在很大程度上让管理更加便捷和人性化

- -

废话不多说,我们直接来看代码吧,首先我们先来看看使用 maven 来进行多环境的配置

-

maven

-

pom.xml 文件

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!-- pom.xml <project>标签下配置环境变量名 -->
<profiles>
<profile>
<!-- 本地开发环境 -->
<id>dev</id>
<properties>
<profileActive>dev</profileActive>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<!-- 正式环境 -->
<id>prod</id>
<properties>
<profileActive>prod</profileActive>
</properties>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
</profile>
</profiles>

<!-- pom.xml <project>标签下配置打包根据环境导入的文件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<!-- 文件加载配置-->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>application*.yml</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>application.yml</include>
<include>application-${profileActive}.yml</include>
</includes>
</resource>
</resources>
</build>
-

application*.yml 文件

-

根据上面我们的配置,我们首先排除 resources 目录下的所有 application*.yml 文件,然后再导入 application.ymlapplication-${profileActive}.yml 文件,那么通常在 application.yml 文件中我们放置一些与环境无关的配置,在 application-${profileActive}.yml 文件中根据不同环境配置不同的属性(比如:数据库连接,日志等级等配置)

-

根据 SpringBoot 加载资源文件的顺序,如果我们在 application-${profileActive}.yml 文件中配置了与 application*.yml 文件相同的属性,那么 application-${profileActive}.yml 会覆盖掉 application*.yml 文件中相同属性配置

-

application.yml

-
1
2
3
4
5
# 动态激活运行的环境,默认是 dev
# 当然你也可以在你的 pom.xml 文件中进行默认激活的环境更改
spring:
profiles:
active: @profileActive@
-

gradle

-

编译打包

-

部署

-

关于应用的部署,可以参考IDEA 之 SpringBoot 应用部署,这里不再过多进行说明

-

附录

-
    -
  • SpringBoot 2.1.6.RELEASE 官方指南
  • -
  • SpringBoot 中文指南
  • -
  • Gradle Builds From Apache Maven
  • -
  • 灵活强大的构建系统 Gradle
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/02/22/springboot6/index.html b/2020/02/22/springboot6/index.html deleted file mode 100644 index 4f3553950..000000000 --- a/2020/02/22/springboot6/index.html +++ /dev/null @@ -1,497 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(六)日志管理 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(六)日志管理 -

- - -
- - - - -

日志是我们项目开发过程中必不可少的一个方面, 当我们项目引入了 spring-boot-starter-web 这个 jar 包,会自动引入相关的一些日志相关的 jar 包,比如,其实在项目中可供我们选择的 jar 包有很多,比如 log4jlog4j2logback(现在使用最多),slfj等,在具体使用时并不会直接去使用log4jlog4j2而是使用slfj作为门面,具体的实现是通过可插拔的方式提供。logback实际是在log4j之后作者重新写的一个日志框架。本篇文章主要讲logback在项目中的应用

- -

logback-spring.xml

-

文件名使用 logback-spring.xml,位于 resource 路径下

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/02/25/springboot7/index.html b/2020/02/25/springboot7/index.html deleted file mode 100644 index 3d0d19679..000000000 --- a/2020/02/25/springboot7/index.html +++ /dev/null @@ -1,737 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(七)注解 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(七)注解 -

- - -
- - - - -

SpringBoot 与 SpringCloud 微服务技术栈体系本质就是围绕注解来展开,这些注解在微服务框架中扮演非常重要的角色,每个注解都有他的应用场景,通过一些注解的组合让 SpringBoot 与 SpringCloud 开发变的简单和高效,本篇文章我们就来汇总 SpringBoot 相关的注解

-

本篇文章基于如下版本

-
    -
  • Spring:5.1.8 RELEASE
  • -
  • SpringBoot:2.1.6 RELEASE
  • -
- -
-

由于本篇包含众多注解,请配合 Ctrl + F (或 + F)使用
-绿色基础 的注解
-红色常用 的注解

-
-

由于 SpringBoot 的基础是 Spring,SpringBoot 相关部分注解都是在 Spring 的基础注解上的再组合,因此我们先来学习 Spring 的相关基础注解,在注解中大部分注解的修饰中包含已下几个注解,这里统一来说明下

-

Java 相关

-

@Documented

-

在默认情况下Documented注解表明这个注释是由 javadoc 记录的也有类似的记录工具。 如果一个类型声明被注释了文档化,它的注释成为公共API的一部分

-
    -
  • 路径:java.lang.annotation
  • -
  • 引入:JDK 1.5 开始引入
  • -
-

@Inherited

-

说明子类可以继承父类中的该注解,注释类型是自动继承的。

-
    -
  • 如果注释类型声明中存在继承的元注释,并且用户在类声明中查询该注释类型,并且该类声明中没有该类型的注释,则将自动查询该类的超类以获取注释类型。重复此过程,直到找到该类型的注释,或到达类层次结构(对象)的顶部为止。
  • -
  • 如果没有超类对此类型进行注释,则查询将指示所讨论的类没有此类注释。
  • -
-
-

请注意,如果带注释的类型用于 注释除类之外 的任何内容,则此元注释类型无效。还要注意,此元注释仅使注释从超类继承;已实现的接口上的注释无效

-
-
    -
  • 路径:java.lang.annotation
  • -
  • 引入:JDK 1.5 开始引入
  • -
-

@Retention

-

注解的保留位置,RetentionPolicy 是提供的策略枚举

-
    -
  1. SOURCE:注解将被编译器丢弃,比如:@Override,@SupressWarnings
  2. -
  3. CLASS:注释将由编译器记录在类文件中,但不必在运行时由VM保留。这是默认的行为
  4. -
  5. RUNTIME:注释由编译器记录在类文件中,并由在运行时由VM保留,因此可以通过反射方式读取它们,比如 @Deprecated
  6. -
-
-

@Retention(RetentionPolicy.RUNTIME) // 作用于运行期

-
-
    -
  • 路径:java.lang.annotation
  • -
  • 引入:JDK 1.5 开始引入
  • -
-

@Target

-

用于描述注解的使用范围(即:被描述的注解可以用在什么地方),ElementType 是提供的策略枚举

-
    -
  1. ANNOTATION_TYPE:用于注释类型
  2. -
  3. CONSTRUCTOR:用于描述构造器
  4. -
  5. FIELD:用于描述字段(包括枚举常量)
  6. -
  7. LOCAL_VARIABLE:用于描述局部变量
  8. -
  9. METHOD:用于描述方法
  10. -
  11. PACKAGE:用于描述包
  12. -
  13. PARAMETER:用于描述参数
  14. -
  15. TYPE:用于描述类、接口(包括注解类型) 或enum声明
  16. -
  17. TYPE_PARAMETER:用于参数类型
  18. -
  19. TYPE_USE:用于使用类型
  20. -
-
-

@Target(ElementType.ANNOTATION_TYPE) // 作用于注释类型

-
-
    -
  • 路径:java.lang.annotation
  • -
  • 引入:JDK 1.5 开始引入
  • -
-

Spring 相关

-

context.annotation

-

路径:org.springframework.context.annotation

-

@Bean

-
    -
  • 引入:Spring 3.0 开始引入
  • -
-

@ComponentScan

-

一个组合注解,默认会装配标识了@Controller,@Service,@Repository,@Component 注解到 Spring 容器中

-
    -
  • 引入:Spring 3.1 开始引入
  • -
-
-

@ComponentScan 与 @ComponentScans

-
-

@Conditional

-

可以根据代码中设置的条件装载不同的 bean,在设置条件注解之前,先要把装载的 bean 类去实现 Condition 接口,然后对该实现接口的类设置是否装载的条件。

-

SpringBoot 注解中的 @ConditionalOnProperty,@ConditionalOnBean 等以 @Conditional* 开头的注解,都是通过集成了@Conditional 来实现相应功能

-
    -
  • 引入:Spring 4.0 开始引入
  • -
-

@Configuration

-

用于自定义配置类,可替换 XML 配置文件,被注解的类内部包含有一个或多个被 @Bean 注解的方法,这些方法会将被 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描,并用于构建 bean 定义,初始化 Spring 容器

-
    -
  • 引入:Spring 3.0 开始引入
  • -
-

@DependsOn

-

@Description

-

@EnableAspectJAutoProxy

-

@EnableLoadTimeWeaving

-

@EnableMBeanExport

-

@Import

-

通过导入的方式实现把实例加入 SpringIOC 容器中。可以在需要时将没有被 Spring 容器管理的类导入至 Spring 容器中

-
    -
  • 引入:Spring 3.0 开始引入
  • -
-

@ImportResource

-

和 @Import 类似,区别就是 @ImportResource 导入的是配置文件

-
    -
  • 引入:Spring 3.0 开始引入
  • -
-

@Lazy

-

@Primary

-

@Profile

-

@PropertySource

-
-

@PropertySource 与 @PropertySources

-
-

@Role

-

@Scope

-

stereotype

-

路径:org.springframework.stereotype

-

@Component

-

是一个元注解,意思是可以注解其他类注解,比如:@Controller @Service @Repository 带此注解的类被看做组件,当使用基于注解的配置和类路径扫描的时候,这些类就会被实例化。其他类级别的注解也可以被认定为是一种特殊类型的组件,比如@Controller(注入服务),@Service(注入 DAO),@Repository(实现 DAO 访问)。

-

@Component 泛指组件,当组件不好归类时,我们可以使用这个注解进行标注,作用相当于 XML 配置,<bean id="" class="" >

-
    -
  • 引入:Spring 2.5 开始引入
  • -
-

@Controller

-

@Indexed

-

@Repository

-

@Service

- -

SpringBoot 相关

-

spring-boot

-

spring-boot:2.1.6.RELEASE

-

@SpringBootConfiguration

-

路径:org.springframework.boot

-

context.properties

-

路径:org.springframework.boot.context.properties

-
@ConfigurationProperties
-
@ConfigurationPropertiesBinding
-
@DeprecatedConfigurationProperty
-
@EnableConfigurationProperties
-
@NestedConfigurationProperty
-

convert

-

路径:org.springframework.boot.convert

-
@DataSizeUnit
-
@Delimiter
-
@DurationFormat
-
@DurationUnit
-

jackson

-

路径:org.springframework.boot.jackson

-
@JsonComponent
-

web.server

-

路径:org.springframework.boot.web.server

-
@LocalServerPort
-

web.servlet

-

路径:org.springframework.boot.web.servlet

-
@ServletComponentScan
-

spring-boot-autoconfigure

-

spring-boot-autoconfigure:2.1.6.RELEASE

-

org.springframework.boot.autoconfigure

-

@AutoConfigurationPackage

-

@AutoConfigureAfter

-

@AutoConfigureBefore

-

@AutoConfigureOrder

-

@EnableAutoConfiguration

-

@ImportAutoConfiguration

-

@SpringBootApplication

-

表示一个配置类,它声明一个或多个 Bean 方法并且会触发自动配置以及组件扫描,这是一个很便捷的注解,@SpringBootApplication 相当于同时使用 @Configuration、 @EnableAutoConfiguration、 @ComponentScan这三个注解

-
1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.TYPE)             // 当前注解所修饰的对象范围:用于描述类,接口,enum声明
@Retention(RetentionPolicy.RUNTIME) // 作用于运行期
@Documented // 生成文档
@Inherited // 继承
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

}
-
    -
  • 引入:SpringBoot 1.2.0开始引入
  • -
-

@SpringBootConfiguration

-

condition

-

路径:org.springframework.boot.autoconfigure.condition

-
@ConditionalOnBean
-
@ConditionalOnClass
-
@ConditionalOnCloudPlatform
-
@ConditionalOnExpression
-
@ConditionalOnJava
-
@ConditionalOnJndi
-
@ConditionalOnMissingBean
-
@ConditionalOnMissingClass
-
@ConditionalOnNotWebApplication
-
@ConditionalOnProperty
-
@ConditionalOnResource
-
@ConditionalOnSingleCandidate
-
@ConditionalOnWebApplication
-

data

-

路径:org.springframework.boot.autoconfigure.data

-
@ConditionalOnRepositoryType
-

domain

-

路径:org.springframework.boot.autoconfigure.domain

-
@EntityScan
-

flyway

-

路径:org.springframework.boot.autoconfigure.flyway

-
@FlywayDataSource
-

liquibase

-

路径:org.springframework.boot.autoconfigure.liquibase

-
@LiquibaseDataSource
-

quartz

-

路径:org.springframework.boot.autoconfigure.quartz

-
@QuartzDataSource
-

SpringCloud 相关

-

JPA 注解

-

@Column

-

@Entity

-

@GeneratedValue

-

@Id

-

@JoinColumn

-

@JsonIgnore

-

@MappedSuperClass

-

@NoRepositoryBean

-

@OneToOne、@OneToMany、@ManyToOne

-

@SequenceGeneretor

-

@Transient

-

异常

-

@ControllerAdvice

-

@ExceptionHandler

-

其他注解

-

@Autowired

-

@Inject

-

@JsonBackReference

-

@PathVariable

-

@Qualifier

-

@RepositoryRestResourcepublic

-

@RequestMapping

-

@Resource

-

@ResponseBody

-

@RestController

-

@Value

-

附录

-
    -
  • Spring Boot 注解如何系统的学习
  • -
  • SpringBoot 系列(三)Spring Boot 自动配置
  • -
  • Spring boot 2.x注解Annotation大全
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/02/26/springboot8/index.html b/2020/02/26/springboot8/index.html deleted file mode 100644 index e97665996..000000000 --- a/2020/02/26/springboot8/index.html +++ /dev/null @@ -1,509 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(八)SpringApplication 源码分析 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(八)SpringApplication 源码分析 -

- - -
- - - - -

正如我们看到的 SpringBoot 应用启动入口类,main() 方法中一行简单 SpringApplication.run(MyApplication.class, args); 就可以将 SpringBoot 应用给启动了。那么它肯定是在SpringApplication中做了大量的工作,才能将应用启动,因此本篇文章我们来一起看看这个核心的类

-

SpringApplication 类可以从 Java 的 main 方法中引导和启动 Spring 的应用,默认情况下它会按照如下的启动步骤

-
    -
  1. 创建一个恰当的 ApplicationContext 实例(取决于你的 classpath 路径)
  2. -
  3. 注册一个 CommandLinePropertySource 将命令行参数作为 Spring 的属性(换句话说,可以通过命令行来传递当前应用所需要的一些属性)
  4. -
  5. 刷新应用的 Context(内容上下文),并加载所有单例的 beans
  6. -
  7. 触发每个 CommandLineRunner beans
  8. -
- -

大多数情况下,我们在 main() 方法中直接使用静态的 run(Class, String[]) 方法启动加载应用,对于一些高级的配置,我们可以创建一个 SpringApplication 实例,在运行之前进行自定义的配置

-
1
2
3
4
5
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
// ... customize application settings here
application.run(args)
}
-

SpringApplication 可以读取不同来源的 bean 信息,通常推荐我们使用被 @Configuration 修饰的单例类来启动应用,然而你也可以在多种来源去设置你的 source

-
    -
  • 使用AnnotatedBeanDefinitionReader加载完全限定类名
  • -
  • 使用XmlBeanDefinitionReader读取本地 XML 资源,或者使用GroovyBeanDefinitionReader 读取 Groovy 脚本去加载
  • -
  • 使用ClassPathBeanDefinitionScanner扫描得到包的名字
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/02/27/fuck-gfw/index.html b/2020/02/27/fuck-gfw/index.html deleted file mode 100644 index 5977bc41d..000000000 --- a/2020/02/27/fuck-gfw/index.html +++ /dev/null @@ -1,594 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -专治各种网络不服 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 专治各种网络不服 -

- - -
- - - - -

众所周知国内的开发已经由原来的“致敬”到现在软件生态领域的“引领”(目前来说还未到真正引领)世界技术发展,但是一个问题始终还是未能有所突破,作为中国的开发者每次在面对新的技术在环境搭建就劝退了一众人,很多开发所依赖的项目资源都来自国外服务,而由于中国的特殊,对很多国外服务的限制,让你在开始的第一阶段总是碰的鼻青脸肿,把大量的时间浪费在环境搭建的等待上,虽然现在很多开源组织或者一线大厂提供了相应的镜像服务方便国内的开发者,但很多都还需要我们自行去更改或者解决这些问题,本篇文章就是我的开发之路上的各种网络问题的解决办法

- -

Gradle

-

Gradle 作为新一代的包管理工具,早期作为 Android 项目的御用包管理,渐渐的越来越多的服务端项目也开始在使用 Gradle 来进行管理,至于 Gradle 和 Maven 的对比,我这里不做评论,请移步 Gradle 官方网站对两个包管理的比较 Gradle vs Maven Comparison

-
-

Gradle 镜像地址:https://services.gradle.org/distributions
-Gradle 腾讯镜像:https://mirrors.cloud.tencent.com/gradle

-
-

单项目配置

-

Android 项目

-

在 Android 项目中主要有下面这些配置文件

-
    -
  • build.gradle -
      -
    • 项目级别:在项目根目录,定义项目中所有模块共用的 Gradle 代码库和依赖项
    • -
    • 模块级别:在模块根目录,用于为其所在的特定模块配置构建设置,可以通过配置这些构建设置提供自定义打包选项(如额外的构建类型和产品变种),以及替换 main/ 应用清单或顶层 build.gradle 文件中的设置
    • -
    -
  • -
  • settings.gradle:项目的根目录下,用于指示 Gradle 在构建应用时应将哪些模块包含在内
  • -
  • xxxxx.gradle:模块配置文件,可将冗长的配置信息分块进行配置,比如依赖的版本统一管理等
  • -
-
-

更详细的介绍请查看 Android 官方说明 配置构建

-
-

项目的依赖仓库镜像配置,在项目级别的 build.gradle 文件中,如下进行镜像的指定示例

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
buildscript {
repositories {
// 添加或修改这里指向的镜像仓库地址,默认使用,https://maven.google.com/
google()
// 如果需要指定 google 的镜像地址,可注释掉上面的 google()默认配置,使用下面的显示指定配置
// google{
// url 'https://maven.aliyun.com/repository/google'
// }
jcenter()
maven {
// 比如修改这里的镜像指向地址
// url 'https://jitpack.io'
url 'https://maven.aliyun.com/repository/public'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
// 添加或修改这里指向的镜像仓库地址,默认使用,https://maven.google.com/
google()
jcenter()
maven {
// 比如修改这里的镜像指向地址
// url 'https://jitpack.io'
url 'https://maven.aliyun.com/repository/public'
}
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}
-
-

我们所依赖的 jar 文件,阿里云镜像中不一定都有,所以我们需要根据具体的项目实际情况调整

-
-

SpringBoot 项目

-

在 SpringBoot 项目中主要有下面这些配置文件

-
    -
  • build.gradle:主要配置文件,关于项目的依赖关系主要在该文件中配置
  • -
  • settings.gradle:项目信息文件,项目的一些可在这里配置,比如项目名称、子项目信息
  • -
  • xxxxx.gradle:模块配置文件,可将冗长的配置信息分块进行配置,比如依赖的版本统一管理等
  • -
-

如下,build.gradle 配置信息

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
plugins {
id 'org.springframework.boot' version '2.1.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}

group = 'org.incoder'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
sourceCompatibility = 1.8

repositories {
// 显式指定仓库访问地址,以下两个地址推荐阿里云镜像,按顺序执行
maven {
// 指向 阿里云 镜像仓库
url 'https://maven.aliyun.com/repository/public'
}
// maven {
// // 指向 spring 官方仓库
// url 'http://repo.spring.io/release'
// }
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}

test {
useJUnitPlatform()
}
-

对于 SpringBoot 项目,不管是全局镜像配置还是单项目的镜像配置,可能存在 SpringBoot 依赖的插件依赖无法下载,通常表现为卡在 Gradle: Download org.springframework.boot.gradle.plugin-xxxx.pom 这里,那则需要也配置插件镜像,在 settings.gradle 文件最上面加入如下配置

-
1
2
3
4
5
pluginManagement {
repositories {
maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
}
}
-

全局配置

-

在当前系统 ${USER_HOME}/.gradle/ 目录下创建 init.gradle 文件,将 Maven 和 Jcenter 仓库都指向阿里云镜像仓库

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
`// 如果你的 springboot plugin 下载也很慢,也可以全局设置插件下载地址`
pluginManagement {
repositories {
maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
}
}
// 项目依赖第三方包下载地址替换
allprojects{
repositories {

def ALIYUN_CENTRAL_URL = 'https://maven.aliyun.com/repository/central/'
def ALIYUN_JCENTER_PUBLIC_URL = 'https://maven.aliyun.com/repository/public/'
def ALIYUN_GOOGLE_URL = 'https://maven.aliyun.com/repository/google/'
def ALIYUN_GRADLE_PLUGIN_URL = 'https://maven.aliyun.com/repository/gradle-plugin/'
def ALIYUN_SPRING_URL = 'https://maven.aliyun.com/repository/spring/'
def ALIYUN_SPRING_PLUGIN_URL = 'https://maven.aliyun.com/repository/spring-plugin/'
def ALIYUN_GRAILS_CORE_URL = 'https://maven.aliyun.com/repository/grails-core/'
def ALIYUN_APACHE_SNAPSHOTS_URL = 'https://maven.aliyun.com/repository/apache-snapshots/'

all { ArtifactRepository repo ->
if(repo instanceof MavenArtifactRepository){
def url = repo.url.toString()
// central
if (url.startsWith('https://repo1.maven.org/maven2/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_CENTRAL_URL."
remove repo
}
// jcenter
if (url.startsWith('https://jcenter.bintray.com/') || url.startsWith('http://jcenter.bintray.com/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_JCENTER_PUBLIC_URL."
remove repo
}
// google
if (url.startsWith('https://maven.google.com/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_GOOGLE_URL."
remove repo
}
// gradle-plugin
if (url.startsWith('https://plugins.gradle.org/m2/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_GRADLE_PLUGIN_URL."
remove repo
}
// spring
if (url.startsWith('https://repo.spring.io/libs-milestone/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_SPRING_URL."
remove repo
}
// spring-plugin
if (url.startsWith('http://repo.spring.io/plugins-release/') || url.startsWith('https://repo.spring.io/plugins-release/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_SPRING_PLUGIN_URL."
remove repo
}
// grails-core
if (url.startsWith('https://repo.grails.org/grails/core/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_GRAILS_CORE_URL."
remove repo
}
// apache snapshots
if (url.startsWith('https://repository.apache.org/snapshots/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_APACHE_SNAPSHOTS_URL."
remove repo
}
}
}

maven { url ALIYUN_CENTRAL_URL }
maven { url ALIYUN_JCENTER_PUBLIC_URL }
maven { url ALIYUN_GOOGLE_URL }
maven { url ALIYUN_GRADLE_PLUGIN_URL }
maven { url ALIYUN_SPRING_URL }
maven { url ALIYUN_SPRING_PLUGIN_URL }
maven { url ALIYUN_GRAILS_CORE_URL }
maven { url ALIYUN_APACHE_SNAPSHOTS_URL }
}
}
-

Maven

-

单项目配置

-

项目配置文件pom.xml文件中配置镜像地址,标签下添加标签配置阿里云镜像

-
1
2
3
4
5
6
7
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
-

全局配置

-

Maven 默认配置文件地址,Users/<PC_USER_NAME>/.m2目录下,如果没有,则新建一个settings.xml文件,进行镜像的配置

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

<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云central仓库</name>
<url>https://maven.aliyun.com/repository/central/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云public仓库</name>
<url>https://maven.aliyun.com/repository/public/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云Google仓库</name>
<url>https://maven.aliyun.com/repository/google/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云gradle-plugin仓库</name>
<url>https://maven.aliyun.com/repository/gradle-plugin/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云Spring仓库</name>
<url>https://maven.aliyun.com/repository/spring</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云spring-plugin仓库</name>
<url>https://maven.aliyun.com/repository/spring-plugin/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云grails-core插件仓库</name>
<url>https://maven.aliyun.com/repository/grails-core/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云Apache仓库</name>
<url>https://maven.aliyun.com/repository/apache-snapshots/</url>
</mirror>
</mirrors>

<proxies/>
<profiles/>
<activeProfiles/>
</settings>
-
-

具体的配置教程,可参考阿里云云效 maven

-
-

Homebrew

-

Homebrew是 macOS 系统的一款开源的包管理器

-

修改镜像

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# macOS 系统修改 homebrew 镜像地址为清华镜像
git -C "$(brew --repo)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git
git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git
git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git
# 『可选』 如果之前有安装过 font 相关,则更改 font 相关的镜像地址,否则会提示 cask-fonts 不存在
git -C "$(brew --repo homebrew/cask-fonts)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-fonts.git
# 『可选』 如果之前有安装过 drivers 相关,则更改 drivers 相关的镜像地址,否则会提示 cask-drivers 不存在
git -C "$(brew --repo homebrew/cask-drivers)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-drivers.git
# 更换后测试工作是否正常
brew update
# 替换Homebrew Bottles源(以下方式二选一即可)
# 1. bash用户
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles' >> ~/.bash_profile
source ~/.bash_profile
# 2. zsh用户
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles' >> ~/.zshrc
source ~/.zshrc
-

恢复镜像

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# macOS 系统恢复 homebrew 原镜像地址
git -C "$(brew --repo)" remote set-url origin https://github.com/Homebrew/brew.git
git -C "$(brew --repo homebrew/core)" remote set-url origin https://github.com/Homebrew/homebrew-core.git
git -C "$(brew --repo homebrew/cask)" remote set-url origin https://github.com/Homebrew/homebrew-cask.git
# 『可选』 如果之前有安装过 font 相关,则更改 font 相关的镜像地址,否则会提示 cask-fonts 不存在
git -C "$(brew --repo homebrew/cask-fonts)" remote set-url origin https://github.com/Homebrew/homebrew-cask-fonts.git
# 『可选』 如果之前有安装过 drivers 相关,则更改 drivers 相关的镜像地址,否则会提示 cask-drivers 不存在
git -C "$(brew --repo homebrew/cask-drivers)" remote set-url origin https://github.com/Homebrew/homebrew-cask-drivers.git
# 更换后测试工作是否正常
brew update
# 取消 Homebrew Bottles源设置(以下方式二选一)
# 1. bash用户,取消 HOMEBREW_BOTTLE_DOMAIN 设置
vim ~/.bash_profile
source ~/.bash_profile
# 2. zsh用户,取消 HOMEBREW_BOTTLE_DOMAIN 设置
vim ~/.zshrc
source ~/.zshrc
-
-

其他镜像:中科大

-
-

Github

-

资源文件无法加载

-

GitHub 上各种图片都无法加载,不仅仅是头像,包括各个仓库中的图片也是。开始以为是科学上网的问题,又或者是图片本身失效,可是验证下来都是无关的,经过一番折腾查找,原来是 DNS 被污染导致

-

解决方法:通过查看头像等文件的访问地址,了解到这些地址的域名都是 githubusercontent.com,然后通过IP 地址查询可以找到其对应的 IP 地址,并将其相关二级域名一起配置到 Hosts 文件中

-

host 路径

-
    -
  • macOS:/etc/
  • -
  • Windows:C:\Windows\System32\drivers\etc
  • -
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Github start
140.82.114.3 github.com
140.82.112.4 gist.github.com

185.199.108.153 assets-cdn.github.com
185.199.109.153 assets-cdn.github.com
185.199.110.153 assets-cdn.github.com
185.199.111.153 assets-cdn.github.com
# *.githubusercontent.com raw|gist|cloud|camo|avatars0-9|avatars
199.232.96.133 raw.githubusercontent.com
199.232.96.133 gist.githubusercontent.com
199.232.96.133 cloud.githubusercontent.com
199.232.96.133 camo.githubusercontent.com
199.232.96.133 avatars0.githubusercontent.com
199.232.96.133 avatars1.githubusercontent.com
199.232.96.133 avatars2.githubusercontent.com
199.232.96.133 avatars3.githubusercontent.com
199.232.96.133 avatars4.githubusercontent.com
199.232.96.133 avatars5.githubusercontent.com
199.232.96.133 avatars6.githubusercontent.com
199.232.96.133 avatars7.githubusercontent.com
199.232.96.133 avatars8.githubusercontent.com
199.232.96.133 avatars.githubusercontent.com
-

git clone 慢的想砸电脑

-

方式一:设置代理

-

这个无解,只能在开启代理的前提下,也给终端设置代理

-
1
2
3
4
# 只对 github.com,这里的 port 端口,需要你本地HTTP代理的端口来设置
git config --global http.https://github.com.proxy socks5://127.0.0.1:port
# 取消代理
git config --global --unset http.https://github.com.proxy
-

方式二:Gitee 中转

-

另一种方式,适用于你需要获取 GitHub 源码做相关的其他操作时,可以借助于 Gitee 来作为中转

-

improt-github-to-gitee

-

可以参考大佬的手把手教你

- -

方式三:cnpmjs镜像中转

-

在你需要 clone 的仓库地址中,添加 .cnpmjs.orggithub.com 的后面

-

github-cnpmjs

-

方法四:jsdelivr 免费 CDN 加速

-

适用于下载单个文件

- -

yum

-

yum 源配置文件路径:/etc/yum.repo s.d/

-
1
2
3
4
5
6
7
8
9
10
11
# 查看配置,Centos-xx.repo 类型的文件即为源
cd /etc/yum.repos.d && ls
# 备份源,创建 centos.back 文件夹,并将 *.repo 类型文件剪切到 centos.back 文件夹
mkdir centos.back && mv *.repo centos.back
# 下载安装国内源文件,分别下载了阿里和 163 的源文件
wget http://mirrors.aliyun.com/repo/Centos-7.repo
wget http://mirrors.163.com/.help/CentOS7-Base-163.repo
# 清空缓存
yum clean all
# 生成缓存
yum makecache
-

docker

-

docker 镜像源默认是使用的 docker hub(https://hub.docker.com) 的源,为加快效率,我们通常也将镜像源切换到国内镜像

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 修改 docker 源配置文件
vim /etc/docker/daemon.json
# 内容如下,分别是 docker 中国官方镜像,163,中国科学技术大学
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"https://hub-mirror.c.163.com",
"https://ustc-edu-cn.mirror.aliyuncs.com"
]
}
# 保存 daemon.json 文件修改,并退出
:wq
# 重启 daemon 服务
sudo systemctl daemon-reload
# 重启 docker 服务
sudo systemctl restart docker
-

参考

-
    -
  • Homebrew/Linuxbrew 镜像使用帮助
  • -
  • 阿里云公共代理库配置指南
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/02/27/idea-welfare/index.html b/2020/02/27/idea-welfare/index.html deleted file mode 100644 index 5a54ac491..000000000 --- a/2020/02/27/idea-welfare/index.html +++ /dev/null @@ -1,501 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -JetBrains 系列激活 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- JetBrains 系列激活 -

- - -
- - - - -

- -

位于捷克的布拉格的JetBrains公司你可能不是很熟悉,但在开发的江湖中你一定听过他的名号,“Java 开发最好用 IDE,没有之一”,产品很好,但价格也不便宜。虽然有学生优惠,但对于非学生用户来说也是不小的开支。如果你想支持正版,官方还提供了一个开源项目渠道,你可以用你的开源项目进行申请,申请通过可以获得一年JetBrains全家桶的使用权限,这么好的福利当然不能错过。

-

我们先看看正常个人订阅价格图,IDEA:$149.00/1st year,全家桶产品:$249.00/1st year
-idea-price
-如果你工资比较高,建议还是支持一下,废话不多说,我们一起来看看怎么通过开源项目申请使用产品

-

申请地址:https://www.jetbrains.com/shop/eform/opensource?product=ALL

-

按照实际情况填写,大概一周左右,申请通过,JetBrains官方会发送邮件给你,你只需要按照邮件内的文件继续按照步骤进行操作,就可以获得 1 年的使用权限

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/03/24/springboot10/index.html b/2020/03/24/springboot10/index.html deleted file mode 100644 index 30646e355..000000000 --- a/2020/03/24/springboot10/index.html +++ /dev/null @@ -1,506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(十)Mybatis 常用标签 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(十)Mybatis 常用标签 -

- - -
- - - - -

- -

关于 Mybatis 作为在国内普遍使用的 ORM 框架,我们在使用上要掌握常用的标签

-

mybatis-label

-

附录

-
    -
  • 官方教程《XML 映射器》
  • -
  • 官方教程《动态 SQL》
  • -
  • Mybatis常用标签
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/04/07/time1/index.html b/2020/04/07/time1/index.html deleted file mode 100644 index 8c64420ac..000000000 --- a/2020/04/07/time1/index.html +++ /dev/null @@ -1,570 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -时间(一)之基础概念 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 时间(一)之基础概念 -

- - -
- - - - -
-

时间是一种尺度,在物理定义是标量,借着时间,事件发生之先后可以按 过去-现在-未来 之序列得以确定(时间点),也可以衡量事件持续的期间以及事件之间和间隔长短(时间段) —— 维基百科

- -
- -

- -

单位

-

时间的基本国际单位是。定义一秒为 铯-133原子 基态两个超精细能级间跃迁辐射振荡9,192,631,770周所持续的时间,其起点为世界时1958年的开始

-

时区

-

时区(Time Zone)是地球上的区域使用同一个时间定义。1884年在华盛顿召开国际经度会议时,为了克服时间上的混乱,规定将全球划分为24个时区(东西各 12 个时区)。造成时间上的混乱是由于世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差(这个偏差我们通常叫做时差)

-

图片来自维基百科

-

理论时区

-

理论时区以被 15 整除的经线为中心,向东喜两侧延伸 7.5°,即每 15°划分为一个时区。理论时区的时间采用其中央经线(或标准经线)的地方时。所以每差一个时区,区时相差一个小时,相差多少个时区,就相差多少小时

-

法定时区

-

为了避开国界线,有的时区的形状并不规则,而且比较大的国家以国家内部行政分机型为时区界线。例如,中国跨5个时区,但为了使用方便简单并且全国统一使用一个区时,实际上在中国使用东8区的区时一般称为北京时间作为标准时间

-

GMT

-

格林尼治平均时间(英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。
-自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。
-格林尼治标准时间的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时 基于天文观测本身的缺陷,已经被原子钟报时的协调世界时(UTC)所取代

-

UTC

-

协调世界时(英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称UTC)是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林威治标准时间。

-

中华人民共和国采用ISO 8601:2000的国家标准GB/T 7408-2005《数据元和交换格式 信息交换 日期和时间表示法》中亦称之为协调世界时。

-

协调世界时是世界上调节时钟和时间的主要时间标准,它与0度经线的平太阳时相差不超过1秒,并不遵守夏令时(由实验室用足够精确的铯原子钟导出的时间作为原子时,原子时的精确度极高,精度可以达到每2000万年才误差1秒)。协调世界时是最接近格林威治标准时间(GMT)的几个替代时间系统之一。对于大多数用途来说,UTC时间被认为能与GMT时间互换,但GMT时间已不再被科学界所确定。

-

DST

-

夏时制(英文:Daylight Saving Time),又称夏令时、日光节约时间,是一种在夏季月份牺牲正常的日出时间,而将时间调快的做法。通常使用夏时制的地区,会在接近春季开始的时候,将时间调快一小时,并在秋季调回正常时间。目前中国已经弃用DST

-

ISO-8601

-

国际标准ISO 8601,是国际标准化组织的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是2004年12月1日发行的第三版“ISO8601:2004”以替代1998年的第一版“ISO8601:1998”与2000年的第二版“ISO8601:2000”。

-

计算机中的时间

-

JSR-310

-

JSR(Java Specification Requests)是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。310 是一个编号,在 JDK8 中通过这个标准提供了新的改进日期时间的 API

-

相信做 Java 开发,对于JDK 的时间 API(小于JDK8版本)肯定都是吐槽不少,主要问题体现在以下几个方面

-
    -
  1. -

    最开始,Date 既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂
    -而后 JDK1.1 开始,将三项职责分开了

    -
      -
    1. 使用 Calendar 类实现日期和时间字段之间的转换
    2. -
    3. 使用 DateFormat 类来格式化和分析日期字符串
    4. -
    5. Date 只用来承载日期和时间信息
    6. -
    -

    尽管已经区分了各自的职责,但在使用时任然是很不方便

    -
  2. -
  3. -

    谜之 year 和 month

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 定义的月是 0-11表示 1-12 月

    Date date = new Date(2020, 4, 8);
    // 输出结果:Sat May 08 00:00:00 CST 3920
    // 年竟然是 3920 = 2020 + 1900
    // 月竟然是 May = 4 + 1
    System.out.println(date);

    Calendar calendar = Calendar.getInstance();
    calendar.set(2020, 4, 8);
    // 输出结果:Fri May 08 11:27:11 CST 2020
    // 年输出:和预想一致
    // 月输出:还和 Date 一样是输入月份 +1
    System.out.println(calendar.getTime());
    -
  4. -
  5. -

    Date 与 Calendar 类中的所有属性是可变的,线程不安全

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 计算两个日期之间的天数
    public static void main(String[] args) {
    Calendar birth = Calendar.getInstance();
    birth.set(1975, Calendar.MAY, 26);
    Calendar now = Calendar.getInstance();
    System.out.println(daysBetween(birth, now));
    // 连续计算两个 Date 实例的话,第二次会取得 0,因为 Calendar 状态是可变的
    System.out.println(daysBetween(birth, now)); // 显示 0?
    }

    public static long daysBetween(Calendar begin, Calendar end) {
    long daysBetween = 0;
    while(begin.before(end)) {
    begin.add(Calendar.DAY_OF_MONTH, 1);
    daysBetween++;
    }
    return daysBetween;
    }
    -
  6. -
-
-

JSR 310的规范领导者 Stephen Colebourne,同时也是 Joda-Time 的创建者,JSR 310是在Joda-Time的基础上建立的,参考了绝大部分的API

-
-

2038年问题

-

图片来自维基百科

-

现时大部分使用UNIX的系统都是32位的,即它们会以32位有符号整数表示时间类型time_t。因此它可以表示136年的秒数。表示协调世界时间1901年12月13星期五20时45分52秒至2038年1月19日3时14分07秒(二进制:01111111 11111111 11111111 11111111,0x7FFF:FFFF),在下一秒二进制数字会是10000000 00000000 00000000 00000000(0x8000:0000),这是负数,因此各系统会把时间误解作1901年12月13日20时45分52秒(亦有可能回归到1970年)。这时可能会令软件发生问题,导致系统瘫痪

-

当前的解决方案
-把系统由32位转为64位系统。在64位系统下,此时间最多可以表示到292,277,026,596年12月4日15时30分08秒

-

附录

-
    -
  • 时间 • 维基百科
  • -
  • 时区 • 维基百科
  • -
  • UNIX时间 • 维基百科
  • -
  • ISO 8601 • 维基百科
  • -
  • 格林尼治标准时间(GMT) • 维基百科
  • -
  • 协调世界时(UTC) • 维基百科
  • -
  • 计算机世界中的时间概念
  • -
  • JSR 310: Date and Time API
  • -
  • JSR310-新日期API(完结篇)-生产实战
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/04/08/time2/index.html b/2020/04/08/time2/index.html deleted file mode 100644 index ba8cb0266..000000000 --- a/2020/04/08/time2/index.html +++ /dev/null @@ -1,852 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -时间(二)之核心类 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 时间(二)之核心类 -

- - -
- - - - -

上一篇时间(一)文章,我们已经了解学习了时间相关的一些概念和时间相关的其他知识,那么本篇文章开始我们深入在计算机领域中关于时间的相关核心类的知识,先来让我们看看 JSR-310(JDK8+) 和之前(JDK7 之前)的比较

-

- -

JDK7

-

Date

-

Date 是 Java 最早提供用来封装日期时间的类,由于不易于国际化且参数计算不符合日常认知或不正确,很多获取年、月、日、小时等数据的方法都已废弃(@Deprecated),被 Calendar 类的方法替代

-

Date 类有两个关键的成员变量

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 记录当前时间戳
private transient long fastTime;

/*
* If cdate is null, then fastTime indicates the time in millis.
* If cdate.isNormalized() is true, then fastTime and cdate are in
* synch. Otherwise, fastTime is ignored, and cdate indicates the
* time.
* cdate 对象是 BaseCalendar.Date 类,继承自 sun.util.calendar.CalendarDate 包含很多已计算好的日期时间相关变量,如 dayOfWeek(所在星期的第几天),leapYear(是否闰年)等
* 如果 cdate 对象为空,用 fastTime 变量代表精确到毫秒的时间
* 如果 cdate.isNormalized() 方法返回 true,则 fastTime 和 cdate 已经同步过
* 如果 cdate.isNormalized() 方法返回 false,则忽略 fastTime 的值,使用 cdate 代表时间
*/
private transient BaseCalendar.Date cdate;
-

Date 类提供了两个构造函数

-
1
2
3
4
5
6
7
8
9
10
11
// 无参构造方法,创建当前时间的 Date 类
public Date(){
this(System.currentTimeMillis());
}

// 传入一个 Unix 时间戳,创建特定的时间 Date 类
public Date(long date){
fastTime = date;
}

// 其他通过年月日创建的构造方法已被 Calendar.set() 和 DateFormat.parse() 等方法替代
-
-

静态方法 System.currentTimeMillis() 返回 UTC 时间从 1970-01-01 00:00:00 到现在的总毫秒数,返回类型为 long,也是我们最常见获取当前时间的方法

-
-

java.sql.Datejava.sql.Timejava.sql.Timestamp 类都继承自 java.util.Date 类,是专门用于数据库连接的,由于是继承关系,从数据结构来看它们和父类的区别不大。

-

主要区别在于 Timestamp 类可以表示至纳秒级,其 fastTime 字段从秒之后被截断,毫秒至纳秒精度保持在特有的 nanos 字段中,需要注意 Timestamp 类的纳秒进度可能是 假的

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package java.sql.Timestamp;

/**
* Constructs a Timestamp object
* using a milliseconds time value. The
* integral seconds are stored in the underlying date value; the
* fractional seconds are stored in the nanos field of
* the Timestamp object.
*
* @param time milliseconds since January 1, 1970, 00:00:00 GMT.
* A negative number is the number of milliseconds before
* January 1, 1970, 00:00:00 GMT.
* @see java.util.Calendar
*/
public Timestamp(long time) {
super((time/1000)*1000);
nanos = (int)((time%1000) * 1000000);
if (nanos < 0) {
nanos = 1000000000 + nanos;
super.setTime(((time/1000)-1)*1000);
}
}
-

可以看出,在 fastTime 字段强行截断后,进行毫秒值直接乘以 1000000 的操作后赋给了 nanos 字段,成为了 “只能表示到毫秒的纳秒级精度” 。当然,还可以通过 setNanos(int n) 方法给纳秒数赋精确值。

-

虽然从数据结构上看没有什么特别,但如果涉及到 Timestamp 类的父子类型转换或时间比较,就需要注意这里的“坑”

-
    -
  1. -

    equals() 方法的不对称性 java.sql.Timestamp 类和其父类 java.util.Date 的 equals() 方法是不符合对称性

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void main(String[] args){
    Data date = new Date();
    Timestamp timestamp = new Timestamp(date.getTime());
    System.out.println(date.equals(timestamp));
    System.out.println(timestamp.equals(date));
    }

    // 输出结果
    true
    false
    -
  2. -
  3. -

    时间比较类方法的 “异常” 现象如下,两个有毫秒之差的时间点,after() 方法返回不符合客观事实

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args){
    Long current = System.currentTimeMillis();
    Date t1 = new Timestamp(current);
    Date t2 = new Timestamp(current + 1);
    System.out.println(t2.after(t1));
    System.out.println(t2.compareTo(t1) > 0);
    }

    // 输出结果
    false
    true
    -
  4. -
-

如果在不确定类型的情况下进行时间的比较,尽量使用 compareTo() 方法,可以保证正确性

-

Calendar

-

Calendar 类是一个日历抽象类,提供了一组对年月日时分秒星期等日期信息操作的函数,并针对不同国家和地区的日历提供了相应的子类,即本地化(比如:公历【GregorianCalendar】,佛历【BuddhistCalendar】,日本历【JapaneseImperialCalendar】等)

-

从 JDK1.1 版本开始,在处理日期和时间时系统推荐使用 Calendar 类进行实现。在设计上,Calendar 类的功能要比 Date 类强大太多,而且在实现方式上也比 Date 类要复杂些

-

Calendar 类可以通过静态工厂方法或 new 子类的方式来获得实例

-
    -
  1. -

    getInstance() 方法,有四个重载方法,参数是时区和地区,如果不传会取服务器默认的时区和地区(地区的出现是专门为了区分泰国和日本)

    -
      -
    • getInstance()
    • -
    • getInstance(TimeZone zone)
    • -
    • getInstance(Locale aLocale)
    • -
    • getInstance(TimeZone zone, Locale aLocale)
    • -
    -
  2. -
  3. -

    新建子类对象

    -
    1
    Calendar calendar = new GregorianCalendar();
    -

    Calendar 类可以实现带时区的年月日时分秒星期等对 Unix 时间戳的转换,内部通过子类复杂的 computeTime() 方法进行计算。

    -
      -
    • -

      使用 getTime() 方法返回 java.util.Date 类型的时间

      -
    • -
    • -

      使用 getTimeInMillis() 方法返回当前 Unix 时间戳

      -
    • -
    • -

      通过 get(int field) 方法获取其他年月日等单独信息

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      常量含义
      Calendar.YEAR年份
      Calendar.MONTH月份
      Calendar.DATE日期
      Calendar.DAY_OF_MONTH日期,和上面字段意义完全相同
      Calendar.HOUR12 小时制的小时
      Calendar.HOUR_OR_DAY24 小时制的小时
      Calendar.MINUTE分钟
      Calendar.SECOND
      Calendar.DAY_OF_WEEK星期几
      Calendar.DAY_OF_YEAR今年的第几天
      -
    • -
    -

    也可以通过多个 set 重载方法设定各种值,同时,add() 方法支持对单个值的加减,从而实现时间推移的计算,传入负数即为减

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    System.out.println("当前日期: " + calendar.getTime());
    calendar.add(Calendar.DATE, 10);
    System.out.println("日期+10: " + calendar.getTime());
    calendar.add(Calendar.DATE, -5);
    System.out.println("日期-5: " + calendar.getTime());
    }

    // 输出结果
    当前日期: Fri Apr 10 15:37:46 CST 2020
    日期+10: Mon Apr 20 15:37:46 CST 2020
    日期-5: Wed Apr 15 15:37:46 CST 2020
    -
  4. -
-

GregorianCalendar 对象可以直接使用 isLeapYear(int year) 接口判断是否闰年。需要注意两点

-
    -
  1. Calendar 中 MONTH 的范围: 0-11(Calendar.JANUARY - Calendar.DECEMBER),因此为避免使用错,Calendar.JANUARY 来表示一月
  2. -
  3. Calendar 中 DAY_OF_WEEK 星期日是 1,并不是我们习惯的星期一是 1
  4. -
-

SimpleDateFormat

-

SimpleDateFormat 是一个以语言环境敏感的方式来格式化和分析日期的类,SimpleDateFormat允许选择任何用户自定义的日期时间格式来格式化

-
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args){
Date date = new Date();
System.out.println("格式化之前: " + date);
SimpleDateFormat ft = new SimpleDateFormat("z yyyy-MM-dd HH:mm:ss");
System.out.println("格式化之后: " + ft.format(date));
}

// 输出结果
格式化之前: Fri Apr 10 15:55:42 CST 2020
格式化之后: CST 2020-04-10 15:55:42
-

字符含义

-

日期和时间格式由日期和时间模式字符串中的日期和时间模式字符串指定,从 AZ 和从 az 的无引号字母被解释为表示日期或时间字符串组件的模式字母。可以使用单引号'引用文本以避免解释。"''"表示单引号。所有其他字符都不会被解释;它们只是在格式化期间复制到输出字符串中,或者在解析期间与输入字符串匹配。

-

定义了以下模式字母(所有其他字符AZa - z 保留),详细说明见 SimpleDateFormat 类 Java Doc 中注释明

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
字母日期或时间组成说明示例
GEra designator【是一个代号】TextAD
y(小写)Year【年】Year1996; 96
YWeek year【周年】Year2009; 09
MMonth in year (context sensitive)【一年中的月份(上下文相关)】MonthJuly; Jul; 07
LMonth in year (standalone form)【一年中的月份(独立形式)】MonthJuly; Jul; 07
w(小写)Week in year【一年中的第几周】Number27
WWeek in month【每月的周】Number2
DDay in year【一年中的一天】Number189
d(小写)Day in month【每月的一天】Number10
FDay of week in month【每月的星期几】Number2
EDay name in week【星期几】TextTuesday; Tue
u(小写)Day number of week (1 = Monday, …, 7 = Sunday)
【星期几(1 =星期一,…,7 =星期日)】
Number1
a(小写)Am/pm marker【上午/下午标记】TextPM
HHour in day (0-23)【一天中的小时,0-23表示】Number0
k(小写)Hour in day (1-24)【一天中的小时, 1-24表示】Number24
KHour in am/pm (0-11)【上午/下午 0-11表示】Number0
h(小写)Hour in am/pm (1-12)【上午/下午 1-12表示】Number12
m(小写)Minute in hour【一小时内】Number30
s(小写)Second in minute【分钟】Number55
SMillisecond【毫秒】Number978
z(小写)Time zone【时区】General time zonePacific Standard Time;
PST; GMT-08:00
ZTime zone【时区】RFC 822 time zone-0800
XTime zone【时区】ISO 8601 time zone-08; -0800; -08:00
-

格式化示例

-

给定太平洋时间,2001-07-04 12:08:56,以下示例展示按照自定的格式显示时间

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
日期和时间模式结果
“yyyy.MM.dd G ‘at’ HH:mm:ss z”2001.07.04 AD at 12:08:56 PDT
“EEE, MMM d, ''yy”Wed, Jul 4, '01
“h:mm a”12:08 PM
“hh ‘o’‘clock’ a, zzzz”12 o’clock PM, Pacific Daylight Time
“K:mm a, z”0:08 PM, PDT
“yyyyy.MMMMM.dd GGG hh:mm aaa”02001.July.04 AD 12:08 PM
“EEE, d MMM yyyy HH:mm:ss Z”Wed, 4 Jul 2001 12:08:56 -0700
“yyMMddHHmmssZ”010704120856-0700
“yyyy-MM-dd’T’HH:mm:ss.SSSZ”2001-07-04T12:08:56.235-0700
“yyyy-MM-dd’T’HH:mm:ss.SSSXXX”2001-07-04T12:08:56.235-07:00
“YYYY-'W’ww-u”2001-W27-3
-

TimeZone

-

JDK8+

-

Clock

-

Instant

-

LocalDate

-

LocalTime

-

LocalDateTime

-

OffsetTime

-

OffsetDateTime

-

ZonedDateTime

-

ZoneId

-

Year

-

Month

-

DayOfWeek

-

MonthDay

-

YearMonth

-

总结

-

附录

-
    -
  • Java SE 8 日期和时间
  • -
  • 循序渐进解读计算机中的时间—应用篇(上)
  • -
  • 循序渐进解读计算机中的时间—应用篇(下)
  • -
  • JSR310新日期API(二)-日期时间API
  • -
  • Java中日期时间相关核心类
  • -
  • 《Java 8 实战》● 第 12 章
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/04/09/time3/index.html b/2020/04/09/time3/index.html deleted file mode 100644 index d933fe9e9..000000000 --- a/2020/04/09/time3/index.html +++ /dev/null @@ -1,503 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -时间(三)之格式化解析和时间计算 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 时间(三)之格式化解析和时间计算 -

- - -
- - - - -
-

总结

-

附录

-
    -
  • Convert Date to LocalDate or LocalDateTime and Back
  • -
  • JSR310新日期API(三)-日期时间格式化与解析
  • -
  • Java中日期时间相关核心类
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/04/10/time4/index.html b/2020/04/10/time4/index.html deleted file mode 100644 index bb22bf3d0..000000000 --- a/2020/04/10/time4/index.html +++ /dev/null @@ -1,503 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -时间(四)之主流框架中使用 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 时间(四)之主流框架中使用 -

- - -
- - - - -
-

总结

-

附录

-
    -
  • Java SE 8 日期和时间
  • -
  • JSR310新日期API(二)-日期时间API
  • -
  • Java中日期时间相关核心类
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/04/12/netty-grpc/index.html b/2020/04/12/netty-grpc/index.html deleted file mode 100644 index ba68c1173..000000000 --- a/2020/04/12/netty-grpc/index.html +++ /dev/null @@ -1,508 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Netty(四)之 gRPC | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Netty(四)之 gRPC -

- - -
- - - - -

- -

gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

-

gPRC 是高性能,开源通用RPC框架,可以运行在任何环境中,它是可插拔的并且支持负载均衡,跟踪,运行状况检查和身份校验,从而有效地连接数据中心和跨数据中心的服务,它也适用于分布式计算的最后一段,以将设备,移动应用和浏览器连接到后端的服务

-

gRPC 可以使用 Protocol buffers 作为接口定义语言(IDL:Interface Definition Language)和基础的消息交换格式,通常来说,你可以使用 proto2 这个版本,但是我们建议你使用 proto3 这个版本和 gRPC 一起使用,支持完整的语言,同时避免客户端和服务端版本不一致出现的其他问题

-

gRPC

-

proto3 语言官方使用手册

-

proto3 指南

-

-

gRPC实践

-

gRPC下载

-

编译器安装

-

编写.proto 文件

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/05/01/travel-cs/index.html b/2020/05/01/travel-cs/index.html deleted file mode 100644 index bdaf153fc..000000000 --- a/2020/05/01/travel-cs/index.html +++ /dev/null @@ -1,494 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -长沙 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 长沙 -

- - -
- - - - -
-
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/06/26/springboot9/index.html b/2020/06/26/springboot9/index.html deleted file mode 100644 index db7460e1a..000000000 --- a/2020/06/26/springboot9/index.html +++ /dev/null @@ -1,507 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot(九)普通类如何获取配置文件中的值 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot(九)普通类如何获取配置文件中的值 -

- - -
- - - - -

在之前的 SpringBoot 学习中,我们知道,可以在项目的 application.yml 文件或 application.properties 文件中获取到一些我们项目中的一些静态值。但对于一些非 Spring 所管理的类(比如一些工具类)该如何获取到定义在配置文件中的值呢?而这些工具类中的配置值和对应项目运行的环境有关,我们如果固定写在代码内,每次打包时去更改,显然这种做法不够好,那么本篇文章就来实践一些非 Spring 类如何获取配置的值

- -

《SpringBoot(四)配置文件》 文章中提到,在使用 @Value 注解时,当时提到说 “使用 @Value 获取配置文件中定义的值,通常其类是被 @Controller@Service@Component 等注解修饰,如果是一般普通的类(如一些工具类)并 不能 只接获取到配置文件中定义的值”,显然我们直接按照之前的套路,在application.yml等配置文件中添加自定义的配置,使用 @Value 注解在我们的普通类中是无法获取到的,其实要解决这个问题很简单,那就是将当前的普通类变成一个被 Spring 所接管的类(普通类上使用 @Component 来修饰),这样我们就可以使用了

-

就这?你以为就结束了,那我还写个啥笔记,你在操作完发现你依然拿不到在配置文件中定义的静态值,接下来让我一步步来告诉你怎么来写,和为啥会这么做的原因

-

配置文件

-

参考

-
    -
  1. 工具类用单例模式还是静态方法
  2. -
  3. springboot 工具类加载配置对象
  4. -
  5. 静态方法(工具类)中调用Spring管理的Bean
  6. -
  7. spring实现静态注入
  8. -
  9. 工具类该用单例模式,还是用静态的方式
  10. -
  11. 单例模式工具类中Spring 注入为空 的一些小问题
  12. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/07/01/flowable3/index.html b/2020/07/01/flowable3/index.html deleted file mode 100644 index 64906d53e..000000000 --- a/2020/07/01/flowable3/index.html +++ /dev/null @@ -1,499 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Flowable(三)流程图绘制异常问题 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Flowable(三)流程图绘制异常问题 -

- - -
- - - - -
-

附录

-
    -
  • HttpMediaTypeNotAcceptableException
  • -
  • HttpMediaTypeNotAcceptableException in Spring MVC
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/07/11/springcloud1/index.html b/2020/07/11/springcloud1/index.html deleted file mode 100644 index be9aa0541..000000000 --- a/2020/07/11/springcloud1/index.html +++ /dev/null @@ -1,656 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringCloud(一)Security OAuth2 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringCloud(一)Security OAuth2 -

- - -
- - - - -

什么是 OAuth2

-

用于 REST/APIs 的代理授权框架(delegated authorization framework),基于令牌 Token 的授权,在无需暴露用户密码的情况下,使应用能获取对用户数据的有限访问权限,做到解耦认证和授权

-

OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如:头像,照片,视频等),而在这个过程中无线将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据

- -

什么是 Spring Security

-

Spring Security 是为基于 Spring 的应用程序提供声明书安全保护的安全性框架。Spring Security 提供了完整的安全性解决方案,它能够在 Web 请求级别和方法调用级别处理身份认证和授权。因为基于 Spring 框架,所以 Spring Security 充分利用了依赖注入(Dependency Injection,DI)和面向切面的技术

-

最初,Spring Security 被称为 Acegi Security。Acegi 是一个强大的安全框架,但是它存在一个严重的问题,那就是需要大量的 XML 配置。到了 2.0 版本,Acegi Security 更名为 Spring Security,2.0版本所带来的不仅仅是名字的变化。为了在 Spring 中配置安全性,Spring Security 引入一个全新的、与安全性相关的 XML 命名空间。这个新的命名空间联通注解和一些合理的默认设置,将典型的安全性配置从几百行 XML 减少到十几行。Spring Security 3.0 融入了 SpEL,这将进一步简化 路安全性配置

-

Spring Security 从两个角度来解决安全问题。

-
    -
  • 它使用 Servlet 规范中的 Filter 保护 Web 请求并限制 URL 级别的访问
  • -
  • 它还能够使用 Spring AOP 保护方法调用——借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法
  • -
-

Spring Security OAuth 现状

-

Spring Security OAuth 的模块已被废弃,后续功能已经迁移到 Spring Security 5.2.x 中,但不会再提供 Authorization Server 的功能。

-

为此,随着 Spring Security5.2 发布,Spring 官方强烈鼓励用户开始将其旧版 OAuth2 客户端和资源服务器应用迁移到 Spring Security5.2 中的新支持

-

具体的功能列表请移步查看

-

OAuth2 主要角色

-
    -
  1. 客户应用(Client Application):通常是一个 Web 或无线应用,它需要访问用户的受保护资源
  2. -
  3. 资源服务器(Resource Server):是一个 Web 站点或者 Web service API,用户的受保护数据保存于此
  4. -
  5. 授权服务器(Authorized Server):在客户应用成功认证并获得授权之后,向客户应用颁发访问令牌 Access Token
  6. -
  7. 资源拥有者(Resource Owner):资源的拥有人,想要分享某些资源给第三方应用
  8. -
  9. 客户凭证(Client Credentials):客户的 clientId 和密码用于认证客户
  10. -
  11. 令牌(Tokens):授权服务器在接收到客户请求后,颁发的访问令牌 -
      -
    • 授权码(Authorization Code Token):仅用于授权码授权类型,用于交换获取访问令牌和刷新令牌
    • -
    • 刷新令牌(Refresh Token):用于去授权服务器获取一个新的访问令牌
    • -
    • 访问令牌(Access Token):用于代表一个用户或服务直接去访问受保护的资源
    • -
    • Bearer Token:不管谁拿到 Token 都可以访问资源,像现钞
    • -
    • Proof of Possession(PoP) Token:可以校验 client 是否对 Token 有明确的拥有权
    • -
    -
  12. -
  13. 作用域(Scopes):客户请求访问令牌时,由资源拥有者额外指定的细分权限
  14. -
-

OAuth2 误解

-
    -
  • OAuth 并没有支持 HTTP 以外的协议
  • -
  • OAuth 并不是一个 认证协议
  • -
  • OAuth 并没有定义 授权处理机制
  • -
  • OAuth 并没有定义 Token 格式
  • -
  • OAuth 2.0 并没有定义 加密方法
  • -
  • OAuth 2.0 并不是 单个 协议
  • -
  • OAuth 2.0 仅是 授权框架,仅用于授权代理
  • -
-

OAuth2 运行流程

-

来自 RFC 6749

-

从上面的流转过程,经过下面六个步骤,客户端就能获取到访问资源的令牌,其中第二步是关键,用户要怎样才能给予客户端授权(客户端获取授权的四种模式)

-
    -
  1. (A)用户打开客户端后,客户端要求用户给予权限
  2. -
  3. (B)用户同意给予客户端权限
  4. -
  5. (C)客户端使用上一步获取的授权,向认证服务器申请令牌
  6. -
  7. (D)认证服务器对客户端进行认证后,确认无误,同意发放令牌
  8. -
  9. (E)客户端使用令牌,向资源服务器申请获取资源
  10. -
  11. (F)资源服务器确认令牌无误,同意向客户端开放资源
  12. -
-

Access Token

-

Access Token,顾名思义,就是用来访问受保护资源要用到的令牌。客户端要访问资源服务器上受保护的资源,就必须要有 Access Token 作为通行证。Access Token 由授权服务器生成。客户端再获取了用户授权后才能想授权服务器申请 Access Token

-

An access token is a string representing an authorization issued to the client. … Tokens represent specific scopes and durations of access, granted by the resource owner, and enforced by the resource server and authorization server.

-

从官方的定义来看(RFC 6749 #section-1.4),Access Token 是一个字符串,至少要提供关于 客户端的基本信息(通常是客户端的 ID) 和该客户端获得的权限,权限有一组 scopes 表示,并且 Access Token 是有有效期的

-

关于 Access Token 的具体格式一个字符串标识符呢,还是自包含内容的信息呢,在 OAth2(RFC6749) 中并没有规定

-

Refresh Token

-

Refresh Token 也是有授权服务器生成,当一个 Access Token 过期或者失效时,客户端可以使用 Refresh Token 来获取一个新的 Access Token,这个新的 Access Token 拥有 scopes 范围小于等原来的那个 Access Token 权限

-

Refresh Token 是可选项,如果授权服务器生成了 Refresh Token,它会与 Access Token 一起返回给客户端,我们一起来看一看整个流程

-

refresh-token

-
    -
  1. (A)客户端通过向服务器镜像身份验证来请求访问令牌,授权服务器并显示授权
  2. -
  3. (B)授权服务器对客户端进行身份验证并验证权限授予,如果有效,则颁发访问令牌和刷新令牌
  4. -
  5. (C)客户端携带令牌向资源服务器发出受保护的资源请求
  6. -
  7. (D)资源服务器验证访问令牌,如果有效,返回受保护的资源给客户端
  8. -
  9. (E)重复步骤(C)和(D),直到访问令牌过期,如果客户知道访问令牌已过期,则跳至步骤(G);否则,它将发出另一个受保护的资源请求
  10. -
  11. (F)由于访问令牌无效,因此资源服务器返回无效的令牌错误
  12. -
  13. (G)客户端通过与进行身份验证来请求新的访问令牌,授权服务器并显示刷新令牌,客户端身份验证要求基于客户端类型和授权策略
  14. -
  15. (H)授权服务器对客户端进行身份验证并验证刷新令牌,如果有效,则发出新的访问令牌(并且,新的刷新令牌是可选)
  16. -
-

OAuth2 授权模式

-

关于 OAuth 的授权方式,可以在spring-security-oauth2-autoconfigure-2.1.2.RELEASE.jar jar 文件中

-
    -
  • 类:org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration
  • -
  • 方法:oauth2ClientDetails()
  • -
  • 参数设置:.setAuthrizedGrantTypes() 中 list 包含 -
      -
    • authorization_code
    • -
    • password
    • -
    • client_credentials
    • -
    • implicit
    • -
    • refresh_token
    • -
    -
  • -
-
-

由于标准的 OAuth2 协议中,授权模式并 不包括 refresh_token,但在 Spring Security 的实现中将其归为一种,因此如果需要实现 access_token的刷新,就需要这样一种授权模式

-
-

授权码模式

-

授权码(authorization_code)模式是功能最完整,流程最严谨的授权模式。它的特点就是通过客户端的服务器与授权服务器进行交互,国内常见的第三方平台登录功能基本都是使用这种模式

-

授权码模式

-
    -
  1. (A)用户访问客户端,后者将前者导向到认证服务器
  2. -
  3. (B)用户选择是否给予客户端授权
  4. -
  5. (C)假设用户给予授权,认证服务器将用户导向客户端事先指定的“重定向 URI”,同时附上一个授权码
  6. -
  7. (D)客户端收到授权码,附上早先的“重定向 URI”,向认证服务器申请令牌,这一步是在客户端的后台的服务器上完成,对用户不可见
  8. -
  9. (E)认证服务器核对了授权码和重定向 URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)
  10. -
-

密码模式

-

密码(password)模式是用户把账号和密码直接告诉客户端,客户端使用这些信息向授权服务器申请令牌。这需要用户对客户端高度信任,例如客户端和服务提供商是同一家公司

-

密码模式

-
    -
  1. (A)用户向客户端提供用户名和密码
  2. -
  3. (B)客户端将用户名和密码发给认证服务器,向后者请求令牌
  4. -
  5. (C)认证服务器确认无误后,向客户端提供访问令牌
  6. -
-

客户端模式

-

客户端(client_credentials)模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请权限。严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用的这种模式还是非常方便

-

客户端模式

-
    -
  1. (A)客户端向认证服务器进行身份认证,并要求一个访问令牌
  2. -
  3. (B)认证服务器确认无误后,向客户端提供访问令牌
  4. -
-

简化模式

-

简化(implicit)模式不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌,一般若网站是纯静态页面,则可以采用这种方式

-

简化模式

-
    -
  1. (A)客户端将用户导向认证服务器
  2. -
  3. (B)用户决定是否给予客户端授权
  4. -
  5. (C)假设用户给予授权,认证服务器将用户导向客户端指定的“重定向 URI”,并在 URI 的 Hash 部分包含了访问令牌
  6. -
  7. (D)浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值
  8. -
  9. (E)资源服务器返回一个网页,其中包含的代码可以获取 Hash 值中的令牌
  10. -
  11. (F)浏览器执行上一步获得的脚步,提取出令牌
  12. -
  13. (G)浏览器将令牌发给客户端
  14. -
-

OAuth2/OIDC 相关开源项目

-
    -
  • Redhat Keycloak(Java)
  • -
  • Apereo CAS(Java)
  • -
  • IdentityServer(C#)
  • -
  • OpenId-Connect-Java-Spring-Server
  • -
  • OAuth2全家桶项目
  • -
  • OAuth2全家桶项目
  • -
  • Apache Oltu + Shiro 实现 OAuth2 服务器
  • -
  • Using JWT with Spring Security OAuth
  • -
-

OAuth2 相关书籍

-
    -
  • OAuth2 in Action:主要讲述 OAuth2 协议的原理知识
  • -
  • OAuth 2.0 Cookbook:主要讲述 OAuth2 相关实践
  • -
  • Developer Guide
  • -
-

附录

-
    -
  • OAuth 2.0 最简向导 文章【需翻墙】
  • -
  • 传统 Web 应用中的身份验证技术
  • -
  • Spring Security OAuth 2.0 Roadmap Update
  • -
  • Okta Developer Platform
  • -
  • RFC6749 - The OAuth 2.0 Authorization Framework
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/07/12/springcloud2/index.html b/2020/07/12/springcloud2/index.html deleted file mode 100644 index 794929f51..000000000 --- a/2020/07/12/springcloud2/index.html +++ /dev/null @@ -1,637 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringCloud(二)Security OAuth2 的 四种授权模式 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringCloud(二)Security OAuth2 的 四种授权模式 -

- - -
- - - - -

在上一篇文章中,我们了解了 Security OAuth2 相关的一些基础知识,和整个四种授权模式的交互过程,那么本篇是对四种模式的实践,废话不多说,我们直接开始,SpringCloud 相关的实践代码均托管在rc-cluster-springcloud项目的中,项目使用的一些依赖版本如下

- -
    -
  • gradle:6.1.1
  • -
  • SpringBoot:2.2.6.RELEASE
  • -
  • SpringCloud:Hoxton.SR4
  • -
  • JDK:1.8
  • -
-

实践

-

在实践阶段为了方便,我将资源服务器和授权服务器整合在一个服务上,在后续扩展部分,会提供实际生产环境中的常用做法,先看项目的包依赖

-
1
2
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-oauth2'
-

下面的两点,不管是什么模式的授权方式,写法都是一样

-
    -
  1. 业务 API
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    /**
    * 业务 API
    * 为了方便我直接将 UserInfo 对象放在了 Controller 类中
    *
    */
    @Controller
    public class UserController {

    @GetMapping("/api/userinfo")
    public ResponseEntity<UserInfo> getUserInfo() {
    User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    String email = user.getUsername() + "@gmail.com";
    UserInfo userInfo = new UserInfo();
    userInfo.setName(user.getUsername());
    userInfo.setEmail(email);
    // TODO 不同的授权选择不同的模式
    // 授权码模式
    userInfo.setGrantType("authorization_code");
    // 客户端模式
    userInfo.setGrantType("client_credentials");
    // 密码模式
    userInfo.setGrantType("password");
    // 简化模式
    userInfo.setGrantType("implicit");
    return ResponseEntity.ok(userInfo);
    }

    public static class UserInfo {

    private String name;
    private String email;
    private String grantType;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public String getEmail() {
    return email;
    }

    public void setEmail(String email) {
    this.email = email;
    }

    public String getGrantType() {
    return grantType;
    }

    public void setGrantType(String grantType) {
    this.grantType = grantType;
    }
    }
    }
    -
  2. -
  3. 资源服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /**
    * 资源服务器
    *
    * @author : Jerry xu
    * @date : 2020/7/17 23:45
    */
    @Configuration
    @EnableResourceServer
    public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {

    /**
    * 用来配置对资源的访问控制规则
    * 默认设置下,所有非 /oauth/** 路经下的资源都是被保护的
    *
    * @param http http
    * @throws Exception exception
    */
    @Override
    public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
    .anyRequest()
    .authenticated()
    .and()
    .requestMatchers()
    // 对 /api/** 路经下的资源进行了保护
    .antMatchers("/api/**");
    }
    }
    -
  4. -
-

权码模式

-

代码实现

-
授权服务器配置
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

@Resource
private BCryptPasswordEncoder passwordEncoder;

/**
* 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、
* 默认不支持 Resource Owner Password 授权类型,
* 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证
* 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法
* <ol>
* <li>authorization_code:授权码模式</li>
* <li>password:密码模式</li>
* <li>client_credentials:客户端模式</li>
* <li>implicit:简化模式</li>
* <li>refresh_token:刷新 token 模式</li>
* </ol>
*
* @param clients clients
* @throws Exception exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 在内存中,用于演示,不适用于实际生产环境
clients.inMemory()
// withClient + secret 这两个就是凭证
.withClient("clientapp")
.secret(passwordEncoder.encode("112233"))
// 重定向地址,用于授权成功后跳转
.redirectUris("http://localhost:9001/callback")
// 授权码模式
.authorizedGrantTypes("authorization_code")
// 权限细分
.scopes("read_userinfo", "read_contacts");
}
}
-
Security Web安全配置
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("jerry")
.password(passwordEncoder().encode("xyz"))
.roles("USER");
}
}
-

测试

-

整个过程请看视频

-

https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4

-
    -
  1. 获取授权
    1
    2
    # 浏览器请求地址
    http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo
    -
  2. -
  3. 使用授权码获取 Token
    -
  4. -
  5. 请求资源服务(业务请求)
    -
  6. -
-

如果你没有安装 Postman,对使用 curl 命令比较熟悉,那么可替换上面第 2,3 步操作

-
    -
  • 使用授权码获取 Token
    1
    2
    # 请自行更换 code 参数的值
    curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo"
    -
  • -
  • 请求资源服务(业务请求)
    1
    2
    # 请自行更换 authorization 参数
    curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530"
    -
  • -
-

-

客户端模式

-

代码实现

-
授权服务器配置
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

@Resource
private BCryptPasswordEncoder passwordEncoder;

/**
* 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、
* 默认不支持 Resource Owner Password 授权类型,
* 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证
* 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法
* <ol>
* <li>authorization_code:授权码模式</li>
* <li>password:密码模式</li>
* <li>client_credentials:客户端模式</li>
* <li>implicit:简化模式</li>
* <li>refresh_token:刷新 token 模式</li>
* </ol>
*
* @param clients clients
* @throws Exception exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 在内存中,用于演示,不适用于实际生产环境
clients.inMemory()
// withClient + secret 这两个就是凭证
.withClient("clientapp")
.secret(passwordEncoder.encode("112233"))
// 重定向地址,用于授权成功后跳转
.redirectUris("http://localhost:9001/callback")
// 客户端模式
.authorizedGrantTypes("client_credentials")
// 权限细分
.scopes("read_userinfo", "read_contacts");
}
}
-
Security Web安全配置
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("jerry")
.password(passwordEncoder().encode("xyz"))
.roles("USER");
}
}
-

测试

-

整个过程请看视频

-

https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4

-
    -
  1. 获取授权
    1
    2
    # 浏览器请求地址
    http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo
    -
  2. -
  3. 使用授权码获取 Token
    -
  4. -
  5. 请求资源服务(业务请求)
    -
  6. -
-

如果你没有安装 Postman,对使用 curl 命令比较熟悉,那么可替换上面第 2,3 步操作

-
    -
  • 使用授权码获取 Token
    1
    2
    # 请自行更换 code 参数的值
    curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo"
    -
  • -
  • 请求资源服务(业务请求)
    1
    2
    # 请自行更换 authorization 参数
    curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530"
    -
  • -
-

-

简化模式

-

代码实现

-
授权服务器配置
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

@Resource
private BCryptPasswordEncoder passwordEncoder;

/**
* 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、
* 默认不支持 Resource Owner Password 授权类型,
* 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证
* 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法
* <ol>
* <li>authorization_code:授权码模式</li>
* <li>password:密码模式</li>
* <li>client_credentials:客户端模式</li>
* <li>implicit:简化模式</li>
* <li>refresh_token:刷新 token 模式</li>
* </ol>
*
* @param clients clients
* @throws Exception exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 在内存中,用于演示,不适用于实际生产环境
clients.inMemory()
// withClient + secret 这两个就是凭证
.withClient("clientapp")
.secret(passwordEncoder.encode("112233"))
// 重定向地址,用于授权成功后跳转
.redirectUris("http://localhost:9001/callback")
// 授权码模式
.authorizedGrantTypes("authorization_code")
// 权限细分
.scopes("read_userinfo", "read_contacts");
}
}
-
Security Web安全配置
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("jerry")
.password(passwordEncoder().encode("xyz"))
.roles("USER");
}
}
-

测试

-

整个过程请看视频

-

https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4

-
    -
  1. 获取授权
    1
    2
    # 浏览器请求地址
    http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo
    -
  2. -
  3. 使用授权码获取 Token
    -
  4. -
  5. 请求资源服务(业务请求)
    -
  6. -
-

如果你没有安装 Postman,对使用 curl 命令比较熟悉,那么可替换上面第 2,3 步操作

-
    -
  • 使用授权码获取 Token
    1
    2
    # 请自行更换 code 参数的值
    curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo"
    -
  • -
  • 请求资源服务(业务请求)
    1
    2
    # 请自行更换 authorization 参数
    curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530"
    -
  • -
-

-

测试

-
1
http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=token&scope=read_userinfo&state=abc
-
1
http://localhost:9001/callback#access_token=60fbfadc-f801-4514-a5fb-52c6fd42f6cb&token_type=bearer&state=abc&expires_in=119
-

密码模式

-

https://github.com/spring-projects/spring-boot/issues/11136#issuecomment-381338605

-

代码实现

-
授权服务器配置
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

@Resource
private BCryptPasswordEncoder passwordEncoder;

/**
* 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、
* 默认不支持 Resource Owner Password 授权类型,
* 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证
* 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法
* <ol>
* <li>authorization_code:授权码模式</li>
* <li>password:密码模式</li>
* <li>client_credentials:客户端模式</li>
* <li>implicit:简化模式</li>
* <li>refresh_token:刷新 token 模式</li>
* </ol>
*
* @param clients clients
* @throws Exception exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 在内存中,用于演示,不适用于实际生产环境
clients.inMemory()
// withClient + secret 这两个就是凭证
.withClient("clientapp")
.secret(passwordEncoder.encode("112233"))
// 重定向地址,用于授权成功后跳转
.redirectUris("http://localhost:9001/callback")
// 授权码模式
.authorizedGrantTypes("authorization_code")
// 权限细分
.scopes("read_userinfo", "read_contacts");
}
}
-
Security Web安全配置
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("jerry")
.password(passwordEncoder().encode("xyz"))
.roles("USER");
}
}
-

测试

-

整个过程请看视频

-

https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4

-
    -
  1. 获取授权
    1
    2
    # 浏览器请求地址
    http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo
    -
  2. -
  3. 使用授权码获取 Token
    -
  4. -
  5. 请求资源服务(业务请求)
    -
  6. -
-

如果你没有安装 Postman,对使用 curl 命令比较熟悉,那么可替换上面第 2,3 步操作

-
    -
  • 使用授权码获取 Token
    1
    2
    # 请自行更换 code 参数的值
    curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo"
    -
  • -
  • 请求资源服务(业务请求)
    1
    2
    # 请自行更换 authorization 参数
    curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530"
    -
  • -
-

-

扩展

-

附录

-
    -
  • 芋道 Spring Security OAuth2 存储器
  • -
  • 浅谈 OAuth 2.0 (二) - 授权类型
  • -
  • Spring Security OAuth2
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/07/21/data-sync/index.html b/2020/07/21/data-sync/index.html deleted file mode 100644 index c5e07548f..000000000 --- a/2020/07/21/data-sync/index.html +++ /dev/null @@ -1,512 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -系统间数据同步 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 系统间数据同步 -

- - -
- - - - -

对于不同系统之间的数据同步,业界通常有四种方式来进行数据交互,本篇文章就来聊一聊这四种数据交互方式

- -

共享文件

-
    -
  • 共享目录:可以是同台机器内的硬盘目录或者是挂载一个共享存储。在系统之间同步数据是非常简单,对共享存储中的文件读写就可以了
  • -
  • FTP 或者对象存储:不同系统间的批量文件往往用此方式实现,比如银行的批量回盘文件。数据量比较大的情况下大多采用此种方式,这种方式缺点就是不能实时,做个通知接口做到准实时性
  • -
  • zookeeper:zk 是基于文件的,可以同步简单数据,大数据量不太合适
  • -
-

接口

-

接口可以实时进行数据的交互

-
    -
  • HTTP 接口:这种接口是使用最多的,SpringCloud 所支持的,也是多个已购系统间采用的同步方式。使用方式,可以推送或拉取
  • -
  • RPC 方式:微服务的新起,诞生了很多 RPC 框架,也可以说是 RPC 框架促进了微服务的进步。比较流行的 RPC 框架有 Dubbo,gRPC,Thrift 等
  • -
-

消息队列

-

常用的 MQ 消息队列,在收集日志或者多个系统间准实时同步,优点是系统间解耦,削峰,易扩展等特点

-

数据库

-

同步结构化的数据可以采用数据库的形式,比如一个做批量的服务专门计算或统计业务数据,统计结果写入一个库,业务系统需要的时候直接从这个库读取即可

-

数据库同步的方式我理解大多数的单向少数几个系统间的同步。也可以多个系统间使用,比如用数据库的方式生成唯一 ID,多个系统共享使用

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/07/21/flowable4/index.html b/2020/07/21/flowable4/index.html deleted file mode 100644 index 1f3fe2f4f..000000000 --- a/2020/07/21/flowable4/index.html +++ /dev/null @@ -1,514 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Flowable(四)流程相关知识点 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Flowable(四)流程相关知识点 -

- - -
- - - - -

流程部署

-

流程部署涉及到的表有 act_re_deploymentact_re_procdef,act_ge_bytearry,act_ge_property

-
    -
  • act_re_deployment(部署对象表):存放流程定义的显示名和部署时间,每部署一次增加一条记录
  • -
  • act_re_procdef(流程定义表):存放流程定义的属性信息,部署每个新的流程都会在这张表中添加一条记录,当流程定义的 key 相同时,使用的是版本升级
  • -
  • act_ge_bytearry(资源文件表):存放流程定义相关的部署信息,即流程定义文档的存放处。每部署一次会增加两条记录,一条是关于 bpmn 规则文件,一条是生成的流程图片(如果部署时只指定了 bpmn 一个文件,flowable 会在部署时解析 bpmn 文件内容自动生成流程图)。两个文件都是以二进制形式存储在数据库中
  • -
  • act_ge_property:主键生成策略表
  • -
- -

流程定义

-

流程定义

-

流程的一系列规则定义

-

流程实例

-

代表流程定义的执行实例,例如:员工 A 请假一天,他就必须发出一个请假流程实例申请

-

一个流程实例表示了所有的运行节点,我们可以利用这个对象来了解当前流程实例的进度等信息。流程实例就表示一个流程从开始到结束的最大流程分支,即一个流程中流程实例只有一个。流程实例通常也叫做执行实例的根节点,流程实例和流程定义为一对多的关系

-

执行实例(ProcessInstance Extends Execution),启动流程的时候会首先创建流程实例,然后创建执行实例;流程运转中永远执行的是自己对应的执行实例;当所有的执行实例按照规则执行完毕后,则实例随之结束;flowable 用这个对象去描述流程执行的每一个节点;流程按照流程定义的规则执行一次的过程,就可以表示执行对象 Execution。一个流程中,执行对象可以存着多个,但是实例只能有一个

-

节点

-

开始节点

-

开始节点代表一个规则的开始。在一个规则文件中,开始节点只能是一个,不能是多个。如果是多个则部署的时候会报错。子流程及引用流程也是如此。开始节点只能是一个。启动流程的时候,从开始节点让流程实例运行

-

结束节点

-

结束节点代表一个规则的结束。在一个规则文件中,结束节点可以是多个。如果实例运转到结束节点的时候,则表示当前的执行实例要结束,则流程也将随之结束

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/07/25/springcloud3/index.html b/2020/07/25/springcloud3/index.html deleted file mode 100644 index dbf1e8d23..000000000 --- a/2020/07/25/springcloud3/index.html +++ /dev/null @@ -1,492 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringCloud(三)Security OAuth2 源码分析 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringCloud(三)Security OAuth2 源码分析 -

- - -
- - - - -
-
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/08/05/mac-item2/index.html b/2020/08/05/mac-item2/index.html deleted file mode 100644 index 17b7efde8..000000000 --- a/2020/08/05/mac-item2/index.html +++ /dev/null @@ -1,726 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -iTerm2 日常 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- iTerm2 日常 -

- - -
- - - - -

-

iTerm2是一款优秀强大的第三方终端,相信用 Mac 的开发者,一定听过或者用过 iTerm2 这款终端应用,如果你还没使用过,没关系那么本篇文章就带你了解学习 iTerm2 中的一些常用操作,来提高你的工作效率

- -

安装配置

-

下载

-

iTerm2官网

-
-

注意:
-系统自带的终端默认使用bash;iTerm2默认使用zsh,因此两者切换如下命令

-
-
1
2
# 安装完iTerm 可使用如下命令来切换
chsh -s /bin/[zsh | bash]
-

配置

-

基础设置

-
    -
  1. -

    默认应用
    -MenuBar -> iTerm2 -> Make iTerm2 Default Term
    -iterm2-default

    -
  2. -
  3. -

    全局热键
    -MenuBar -> iTerm2 -> preference -> Keys -> Show/hide iTerm2 with a system-wide hotkey
    -输入设置的快捷键,这里使用⌘,
    -iterm2-hotkey

    -
  4. -
-

安装Oh my zsh

-
    -
  • 方式一:crul
    1
    sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
    -
  • -
  • 方式二:wget
    1
    sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
    -zsh-install
  • -
-

PowerLine

-
1
pip install powerline-status --user
-
-

如果提示:
-command not found: pip
-先执行,再执行上面的字体安装命令

-
-
1
sudo easy_install pip
-

安装PowerFonts

-

为避免后续的使用中,可能会遇到字符乱码的问题,因此安装字体

-

字体库需要首先将项目clone到本地,然后执行源码中的install.sh,根据自己的喜好存放在指定的位置

-
1
2
3
4
5
6
7
8
9
10
11
12
# 进入Documents目录
cd Documents
# 创建文件夹PowerFonts
mkdir PowerFonts
# 进入PowerFonts目录
cd PowerFonts
# clone源码
git clone https://github.com/powerline/fonts.git --depth=1
# 进入fonts目录
cd fonts
# 执行安装脚本
./install.sh
-

设置字体及背景

-
    -
  • -

    设置字体
    -MenuBar -> iTerm2 -> Preferences -> Profiles -> Text -> Change Font,选择Meslo LG字体,L,M,S风格,看个人喜好,这里选择Meslo LG S Powerline
    -iterm2-font

    -
  • -
  • -

    背景设置
    -iterm2-presets

    -
  • -
-

修改主题

-
1
2
3
4
# 打开.zshrc隐藏文件
vim ~/.zshrc
# 修改ZSH_THEME为agnoster
ZSH_THEME="agnoster"
-
-

默认:ZSH_THEME=“robbyrussell”

-
-

辅助

-
    -
  • 高亮插件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd ~/.oh-my-zsh/custom/plugins/
    git clone https://github.com/zsh-users/zsh-syntax-highlighting.git
    vim ~/.zshrc
    # 添加zsh-syntax-highlighting到plugins中,放在git后面
    plugins=(
    git
    zsh-syntax-highlighting
    )
    # 文件最后添加,然后保存并退出
    source ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
    -最后,对配置文件进行生效处理
    1
    source ~/.zshrc
    -
  • -
  • 命令补全
    -安装步骤和上面的高亮插件一致
    1
    2
    3
    4
    5
    cd ~/.oh-my-zsh/custom/plugins/

    git clone https://github.com/zsh-users/zsh-autosuggestions

    vi ~/.zshrc
    -
  • -
  • 设置背景图
    -iTerm2 -> Preferences -> Profiles -> Window -> BackGround Image
  • -
-

主题选择

-

Oh my zsh 本身也包含了很多主题,而我比较喜欢的一款 powerlevel10k 的主题,非常的简洁,满足我的装 X 的需求的同时,极其简单的设置也是我对其爱不释手。

-

之前最早使用的是 powerlevel9k,我的博客里面一些终端的截图就是 powerlevel9k,但随着我的需求越来越多以及加载的插件也越来越多,我不能忍受 iTerm2 在使用 时的效率问题,然后就看到了powerlevel10k,它是在powerlevel9k 的基础上迭代的,大大提高了响应效率和更加简洁的配置等

-

安装

-

官方提供了多种安装方式(Manual,Oh My Zsh,Homebrew 等等),选择你熟系的方式进行安装,我这里使用 Oh My Zsh 方式安装,选择了 gitee 镜像地址进行下载

-
1
2
3
4
5
6
# 1. 下载主题
git clone --depth=1 https://gitee.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
# 2. 在 .zshrc 文件中设置主题
vim ~/.zshrc
# 3. 大概 19 行,设置 ZSH_THEME 参数的值为 powerlevel10k/powerlevel10k
ZSH_THEME="powerlevel10k/powerlevel10k"
-
-

默认安装路径:/Users/<PC USER NAME>/.oh-my-zsh/custom/themes/powerlevel10k

-
-

更新

-

当初选择的什么安装方式,就使用什么方式进行更新,我这里以 Oh My Zsh 方式进行更新

-
1
git -C ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k pull
-

配置

-

只需要在终端()或者 iTerm2 命令窗口中输入 p10k configure 即可,然后根据提示选择即可完成配置,我比较懒,就用的 Pure 样式

-

问题

-

VS Code

-

在 VS Code Terminal 中显示不出来图标或者显示乱码异常等问题,先设置 VS Code 字体看看是否能解决,如果不能再进行排查

-

Open File → Preferences → Settings, 在搜索框中输入terminal.integrated,字体设置为MesloLGS NF.

-

常用工具

-

Homebrew

-
    -
  • Homebrew 是一款自由及开放源代码的软件包管理系统,用以简化macOS系统上的软件安装过程,最初由马克斯·霍威尔(Max Howell)写成
  • -
  • Homebrew Cask,它是一套建立在 Homebrew 基础之上软件安装命令行工具,是 Homebrew 的扩展
  • -
-

官方

-
-

如果你使用的官方安装教程,需要切换 brew 的镜像源,可参考 专治各种网络不服 文章

-
-
安装
-

在你的终端(Terminal/iTerm2)运行如下脚本

-
1
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
-
卸载
-
1
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"
-

国内(推荐)

-
-

按照命令行上面的提示,进行安装

-
-
安装
-
1
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
-
卸载
-
1
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/HomebrewUninstall.sh)"
-

常用命令

-

对于 brew 和 brew cask 的命令基本一致,比如

-
    -
  • 【brew】:brew install [包名]
  • -
  • 【brew cask】:brew cask install [包名]
  • -
-
brew 基础命令
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 安装指定包应用,如:brew install wget
brew install [包名]
# 重新安装指定应用,如:brew reinstall wget
brew reinstall [包名]
# 卸载指定包,如:brew uninstall wget
brew uninstall [包名]
# 列出已安装的软件
brew list
# 全部更新过时软件,如:brew upgrade
brew upgrade
# 指定更新过时的软件,如:brew upgrade wget
brew upgrade [包名]
# 用浏览器打开brew的官方网站
brew home
# 显示当前软件信息,如:brew info wget
brew info [包名]
# 显示包依赖,如:brew deps wget
brew deps [包名]
# 清理所有包的旧版本
brew cleanup
# 清理指定包的旧版本
brew cleanup [包名]
# 查看可清理的旧版本包,不执行实际操作
brew cleanup -n
# 查找指定包
brew search [包名]
# 查看缓存路径
brew --cache
-
brew service
-
1
2
3
4
5
6
7
8
9
10
11
12
# 查看使用brew安装的服务列表
brew services list
# 启动服务(仅启动不注册)
brew services run formula|--all
# 启动服务,并注册
brew services start formula|--all
# 停止服务,并取消注册
brew services stop formula|--all
# 重启服务,并注册
brew services restart formula|--all
# 清除已卸载应用的无用的配置
brew services cleanup
-

Nmap

-

端口扫描必备工具

-
1
2
# 安装 nmap 工具
brew install nmap
-

nmap

-

常用快捷键

-

标签

-
    -
  1. 新建标签:⌘(command) + t
  2. -
  3. 关闭标签:⌘(command) + w
  4. -
  5. 切换标签:⌘(command) + 数字 ⌘(command) + 左右方向键
  6. -
  7. 切换全屏:⌘(command) + enter
  8. -
  9. 查找:⌘(command) + f
  10. -
-

分屏

-
    -
  1. 垂直分屏:⌘(command) + d
  2. -
  3. 水平分屏:⌘(command) + ⇧(shift) + d
  4. -
  5. 切换屏幕:⌘(command) + ⌥(option) + 方向键 ⌘(command) + [ 或 ⌘(command) + tab 所在的数字]
  6. -
  7. 查看历史命令:⌘(command) + ;
  8. -
  9. 查看剪贴板历史:⌘(command) + ⇧(shift) + h
  10. -
-

其他

-
    -
  1. 清除当前行:⌘(command) + u
  2. -
  3. 清屏:⌘(command) + r
  4. -
  5. 列出剪切板历史:⌘(command) + ⇧(shift) + h
  6. -
  7. 命令快照(很实用的一个功能):⌘(command) + ⌥(option) + b -
    -

    大概只能记录 30s 左右的操作

    -
    -
  8. -
-

常见问题

-

文本乱码

-

在一开始使用macOS就已经安装iTerm2来代替了系统自带的Terminal应用,毕竟颜值是决定要不用长期使用的重要因素

-

iTerm2对应的配置文件:.zshrc,Terminal对于的配置文件:.bash_profile.bashrc

-
    -
  • 问题:iTerm2查看本地文件,能正常显示,无乱码,但查看服务器上文件,出现乱码
  • -
  • 原因:本地iTerm2终端和服务器字符集不一致,造成乱码,macOS默认Terminal应用是utf-8,而iTerm2默认没有设置utf-8编码
  • -
  • 解决办法:给本地的.zshrc设置字符集编码
    1
    2
    3
    4
    5
    6
    7
    # 使用vim打开.zshrc文件
    vim ~/.zshrc
    # 在文本内容末尾添加以下两行内容进行字符编码设置
    export LC_ALL=en_US.UTF-8
    export LANG=en_US.UTF-8
    # 保存文件内容,退出vim模式,并使刚刚设置的内容生效
    source ~/.zshrc
    -
    -

    帮助:可以在本地和服务器上分别使用locale命令来查看,本地和服务器的字符编码是否一致

    -
    -
  • -
-

结束指定进程

-
1
2
3
4
5
6
# 查看指定端口号 lsof -i:端口号
lsof -i:8088
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 72612 blade 18u IPv6 0x21ccddb0352361e5 0t0 TCP *:radan-http (LISTEN)
# kill指定进程
kill -9 72612
-

免密登录服务器

-

一图胜千言,请看图

-

ssh-login

-

代理处理

-

在 Mac 系统上,使用 iTerm2 是一件很享受的过程,很多事情都可以通过命令行直接完成,但是一个致命的问题是,很多连接在国内环境下,异常忙,比如通过命令 clone 或处理 GitHub 上的项目,速度慢的让人抓狂,虽然电脑开启了代理(非全局),但视乎没有什么作用,针对此问题,需要让我们的终端也通过代理

-
    -
  1. install privoxy
    1
    brew install privoxy
    -
  2. -
  3. setting privoxy
    1
    vim /usr/local/etc/privoxy/config
    -
  4. -
  5. config privoxy
    1
    2
    listen-address 0.0.0.0:xxxx
    forward-socks5 / localhost:1080 .
    -
    -

    0.0.0.0 可以让其他设备访问到,若不需要,请修改成用 127.0.0.1;xxxx是HTTP代理的默认端口;
    -localhost:1080 是 SOCKS5(shadowsocks) 默认的地址,可根据需要自行修改,且注意不要忘了最后有一个空格和点号。

    -
    -
  6. -
  7. start privoxy
    1
    2
    3
    4
    5
    6
    # 因没有安装在系统目录内,所以启动的时候需要打全路径
    sudo /usr/local/sbin/privoxy /usr/local/etc/privoxy/config
    # 查看是否启动成功(1087 端口号换成自己的)
    netstat -na | grep 1087
    # 看到有类似如下信息就表示启动成功了
    tcp4 0 0 *.1087 *.* LISTEN
    -代理端口查看
    -shadowsocks-proxy
  8. -
  9. use proxy -
      -
    • temp proxy
      -如果关闭终端标签页或窗口,功能就会失效 -
        -
      • star proxy
        1
        2
        3
        # 这里的端口号1087,换成你自己的
        export http_proxy='http://localhost:1087'
        export https_proxy='http://localhost:1087'
        -
      • -
      • cancel proxy
        1
        2
        3
        unset http_proxy
        unset https_proxy

        -
      • -
      -
    • -
    • auto proxy -
        -
      • setting ~/.bash_profile
        1
        2
        3
        4
        5
        6
        7
        # 打开.bash_profile 文件
        vim ~/.bash_profile
        # .bash_profile文件最后添加(1087 端口替换成你自己的)
        export http_proxy='http://localhost:1087'
        export https_proxy='http://localhost:1087'
        # 保存文件 :wq 后,使配置生效
        source ~/.bash_profile
        -
      • -
      • 上面的方式也可以在文件(.bash_profile)中加入如下方法,使用时只需要在终端中输入proxy_on命令,关闭输入proxy_off
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        function proxy_off(){
        unset http_proxy
        unset https_proxy
        echo -e "已关闭代理"
        }

        function proxy_on() {
        export no_proxy="localhost,127.0.0.1,localaddress,.localdomain.com"
        export http_proxy="http://127.0.0.1:1087"
        export https_proxy=$http_proxy
        echo -e "已开启代理"
        }
        -
      • -
      -
    • -
    -
  10. -
  11. test
    1
    2
    # 已废弃,只能查看到当前的 IP 地址,其他信息请使用 curl cip.cc 命令
    curl ip.gs
    -proxy-config
    1
    curl cip.cc
    -proxy-config-info
  12. -
-

参考

-
    -
  • Mac终端-iTerm2使用
  • -
  • Homebrew国内如何自动安装(国内地址)
  • -
  • iTerm2 用法与技巧
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/09/13/travel-cz-plan/index.html b/2020/09/13/travel-cz-plan/index.html deleted file mode 100644 index 8ea40a91b..000000000 --- a/2020/09/13/travel-cz-plan/index.html +++ /dev/null @@ -1,590 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -川藏行 —— 行程规划 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 川藏行 —— 行程规划 -

- - -
- - - - -

唠叨了很久的《成都》,这次终于要去见一见你了,成都在我的印象中是一个万物皆可辣,但也少不了让你肉跳的麻,那里有让你每天不重样的美食,有看不完的美景,也有能让你安逸的歌谣《成都》。早在去年十一就被《盛世中华》视频种草,时隔一年,这个心愿要就要被实现,这次的十一目标不仅是四川,还有西藏的风景,这是双份的快乐。

-

曾经记一个人是一篇文章,后来记一个人是一首歌,后来记一个人是一门学科,再后来记一个人是一段代码或语言,到现在记一个人是一个城市,因为那是有你的城市,那里有你的故事,好了废话有点多,先来看看这次出行的规划

- -

四川

-

总计 3 天时间(2020.09.26 —— 2020.09.28),尽管《盛世中华》中的风景很迷人,但并不在此次行程安排中,主要是考虑到这些景点都比较远,且并不是成熟的游玩路线,这次去的地方主要是视频中没有的景点

-

DAY 1

-

第一天主要是在成都市区内游玩,景点包括:锦里,杜甫草堂博物馆,人民公园,以及四川大学望江校区或者她曾读书的西南财经,其实这都不是重点,重点是去吃各种美食(The Sense 醒食,無早 bowl,老龙亭,还有各种小吃和必吃的火锅),对了还要见一个 3 年多未见的老友(团座)

-

DAY 2

-

第二天去一个稍微远点的地方,乐山大佛,小时候看《风云》时还以为那尊大佛是电脑特效,后来才知道这是真的,那我可要去那里找一找有没有聂风留下的雪饮刀,和菩提果,修炼我的武功。如果有时间去爬一爬峨眉山,去看看令狐冲是否还在那里带领峨眉派,那里有没有周芷若留下倚天剑的残骸

-

DAY 3

-

第三天安排上国宝,这圆滚滚的大家伙,好想用手 rua rua 它的毛,行程结束后再去 mars 上推荐的书店(文轩 BOOKS,無早 bookstore),在那里看一看书,吃一吃甜点,品一品生活,晚上可以去建设路,去感受下城市烟火

-

拉萨

-

共计 8 天(2020.09.29 —— 2020.10.06),拉萨由于是非常陌生,因此选择跟团游的方式,这样既节省时间又省钱(我只是一个搬砖工人),因此选择了 5 天 4 夜(DAY 2 —— DAY 6)的跟团游玩,极大可能是走马观花式标准的游客方式

-

路线参考

-
    -
  • 以拉萨为中心,由近及远,最负盛名的纳木错(路程较远,往往需要在湖边村镇留宿一晚)和羊卓雍措(可当他往返)
  • -
  • 向南,山南人文线,相对比较小众,山南是西藏的“旧都”,人文圣地估计众多,包括西藏第一个真正意义上的寺院:桑耶寺,常见的路线是 2 天,包含了拉姆拉错,思金拉错以及格鲁三大寺当中最古老的甘丹寺(在拉萨达孜县)
  • -
  • 向东去往林芝,这里衔接川滇,海拔较低,森林茂盛,有西藏小江南美陈,全程会路过巴松措,鲁朗林海,雅鲁藏布江(峡谷),南迦巴瓦峰,3 天的行程,再加 2 天可抵达波密,然乌湖
  • -
  • 向西南是日喀则,这里是后藏的中心,古今地位仅次于拉萨,除了在此拜访扎什伦布斯之外,跟多人选择来到这得目的是向南 200 公里走进珠峰大本营,去看一下世界第一高峰,常规路线会带上纳木错,往返总计 5 天
  • -
  • 向西北,是“西藏的西藏”阿里一线,人迹罕至,痛并快乐,经过纳木错穿越羌塘无人区,抵达狮泉河,冈仁波齐,古格王朝,玛旁雍错,常规路线 7—10 天
  • -
-
-

贪心路线,从拉萨出,去程走阿里线,回程走日喀则线,一个西部大环线,耗时 13—15 天

-
-

当然上面是整个拉萨可以游玩的框架,而我们小团队并不是和上图完全一致,只是其中的一部分

-

DAY 1

-

由于是第一次来高原地带,所以第一天就不进行大运动量的活动,就在拉萨市区走一走,了解藏族风土人情,大致包括以下:布达拉宫,文成公主,八廓街,大昭寺,总之看心情和看身体状况来灵活应变

-

DAY 2

-

行程安排:拉萨 - 念青唐古拉山 - 纳木错圣象天门日出日落 - 住圣象天门
-生活住宿:早餐自理,午餐、晚餐含,住营地帐篷或附近民宿(住宿环境别抱太大幻想)
-重点关注:纳木错圣象天门

-

DAY 3

-

行程安排:圣象天门 - 那根拉山口 - 藏北草原 - 东古拉山 - 尼木 - 日喀则
-生活住宿:早餐、午餐、晚餐含,住三星酒店
-重点关注:藏北草原

-

DAY 4

-

行程安排:日喀则 - 定日 - 嘉措拉山口/加乌拉山口 - 珠穆朗玛国家公园 - 珠峰大本营 - 民宿/营地帐篷
-生活住宿:早餐、午餐、晚餐含,住营地帐篷或附近民宿(住宿环境别抱太大幻想)
-重点关注:珠峰大本营的星空

-

DAY 5

-

行程安排:定日 - 扎什伦布寺 - 日喀则
-生活住宿:早餐自理,午餐、晚餐含,住三星酒店
-重点关注:日喀则

-

DAY 6

-

行程安排:日喀则 - 卡若拉冰川 - 羊卓雍措 - 拉萨
-生活住宿:早餐、午餐含,晚餐自理,晚上返回拉萨
-重点关注:羊卓雍措

-

DAY 7 —— DAY 8

-

这两天是自由时光,没有特殊的安排

-

注意事项

-

由于此次去往西藏相对比较偏远,加之藏族人民的生活语言等和我们都不一样,生活环境和其他地方相差较大,异常在出行前带好一些必备的用品,

-

行李指南

-
    -
  1. 证件,一次性洗漱用品,水杯等
  2. -
  3. 阿咖酚散与葡萄糖粉,有效缓解高反,晕车药/贴,感冒发烧,消炎等药物
  4. -
  5. 防风防雨的衣服很有必要,羽绒服,保暖衣物等
  6. -
  7. 进藏四件套:帽子,墨镜,防嗮,唇膏
  8. -
  9. 相机等电子设备
  10. -
  11. 带一些速食,比如泡面,辣条,自住小火锅,矿泉水等
  12. -
-
-

可根据自身需要合理的补充

-
-

何时进藏

-
    -
  1. 6-8 月份为西藏雨季,景色会打折扣
  2. -
  3. 最佳:9-10 月(秋叶),3-4 月(春桃)
  4. -
-

交通方式

-
-

根据自身条件合理选择

-
-
    -
  1. 包车:灵活度高,价格贵(±¥1500/天)
  2. -
  3. 拼车:同车人数少,但可能气场不合
  4. -
  5. 自驾:路况复杂且不能高反,要求极高
  6. -
  7. 跟团:12 人左右,靠窗好位置靠抢
  8. -
  9. 大巴:只去成熟景区和各大购物店
  10. -
-

参考

-
    -
  1. Mars
  2. -
  3. 盛世中华
  4. -
  5. 86元打卡成都必吃小吃
  6. -
  7. 成都·VLOG·下
  8. -
  9. 绝对旅行指南 - 成都篇
  10. -
  11. 绝对旅行指南 - 西藏篇
  12. -
  13. 跨越山海看珠峰
  14. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/10/13/travel-cz-feel/index.html b/2020/10/13/travel-cz-feel/index.html deleted file mode 100644 index d40c26626..000000000 --- a/2020/10/13/travel-cz-feel/index.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -川藏行 —— 人在囧途 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 川藏行 —— 人在囧途 -

- - -
- - - - -

这次假期安排算是毕业后,除去 2020 年春节因为疫情,假期最长的一个,满脑子写着高兴,以至于我出门前一刻,还在处理工作上的事情,浑浑噩噩的追下午的动车和好友汇合,哪知道这只是囧途的开始,在地铁上我快要困死过去,还好没有坐过站,之前有看一眼车站,但是并没有仔细看具体是哪个站,在我的潜意识里一直认为是东站,当我冲到检票口时,我刷身份证发现进不了站,工作人员说我跑错站,我当时一脸茫然(心想跑错站也没关系,只要它经过东站就可以),赶紧查看我的购票记录,发现自己真是跑错了车站而且还不经过东站,距离发车时间就剩 1 小时了

- -

计算了下地铁和检票时间,得出现在赶过去时间非常紧,万一中途出现什么状况,我肯定要凉凉,立马决定改签到东站出发的列车,但是我这次购票是用的 12306 行程积分兑换的,不能直接在手机上进行改签或退票,于是我就赶忙拖着行李去往 1 楼的人工服务窗口,意外的是我恰巧跑向了东边的人工窗口,到了东边绕了一圈发现今天东边窗口不提供服务,于是乎我又从东边一路小跑到西边窗口,今天并不是周末,但人也是不少在排队,我必须在 1 小时之内改签车票,否则我当时购买的车次发车的话,我必须得在原车票的起点站去改签,而原车站后面到达目的地的时间太晚,我根本赶不上飞机(〒︿〒)

-

这次看清了对应业务的办理窗口,我找到其中一个进行排队,焦急地等待着,不清楚前面那位朋友是什么状况,办理他一个人的时间足足等了 10 多分钟,一边看着手机查询车次,一边盯着时间,眼看就要到我了,那工作人员却说暂停服务,她要去趟卫生间,我说我很赶时间,她没理会我,此时的我进退两难,去排另一个队,时间明显不够,我也没办法去插队,两眼一直盯着在我前方的窗口,还好还好,工作人员大概耽误了 3 分多钟回到岗位上,帮我办理改签,由于我当时兑换的是动车,现在发车时间最近的一趟是高铁,需要再补 3000+积分,还好我的积分还够,那还等什么,改改改,改签完成后长舒一口气,再晚 10 分钟,我要么去原发车站去改签,要么重新购买在东站购买车票(此时我并不能在手机上购买车票,因为原行程和我从当前位置出发的行程是冲突的,必须得在会员人工窗口办理退票)

-

12306

-

12306会员积分使用规则

-

12306会员积分使用规则

-

12306车票改签规则

-

12306车票改签规则

-

12306车票退票规则

-

12306车票退票规则

-

改签完车票后,距离发车时间还有 1 个小时,我赶忙在候车厅二楼找到一个饭馆,去点了些东西,吃着东西,一个电话打断了我囫囵吞枣的状态,说是项目线上环境,某页面数据加载不出来,让我看看是什么原因,是不是我中午出发前改的东西影响了,我一脸问号,因为我改动的并没有涉及到页面加载慢的地方,那边说下午要线上演示,让我抽空看看处理下,我连忙说我尽量。距离检票时间还是有 30 多分钟,我放下了手里的鸡腿,开启手机热点,打开电脑,盯着屏幕今天的提交记录,一遍遍排查,自己的没有做相关的操作,当前我也没有心思定下心来找问题的根源,想着先把超时时间调大些,先保证能加载出来数据,至于慢的问题,我到达目的地后再处理,三下五除二调整后,发布测试,并没有什么用😔,于是继续找到对应 API 服务,把对应的服务接口查询做了下优化处理,想让服务正常使用,处理调试后,发现可以接受,来不及多想,赶忙收拾东西去检票上车,一路狂奔

-

到达中转站,和好友汇合,终于停下来了,接过带给我的心心念念的的芋泥奶茶,我就不客气的解决掉它,别喝边说着,快一年没见,怎么又长高了,变瘦了,你都偷偷补了啥,她弱弱的回了句,我不挑食的,我一脸尴尬,她知道我挑食,不吃肉(尤其是外面,老妈做的肉,我还是能吃点),我赶忙说到,我在努力克服这个了,现在还是能稍微吃一点的,两人在候机厅断断续续有一搭没一搭的说着今年发生的事情,说着说着,突如其来的语音播报,由于疫情影响,我们所乘航班推迟,还好没有说是取消,不然今天我要囧到家😶,按照要求填写好行程路径,在线领取健康码后(为啥全国的健康码还没统一,一脸嫌弃,不前一阵子说健康码全国通用嘛),延迟 40 分钟左右,终于登上了前往成都的飞机,在一路的折腾,飞机起飞后,夜景也没来得急看我就睡着了,到达成都后才被朋友叫醒

-

天府之国 - 成都

-

在这里,每件事或物都能和国宝相连,要是这里生活的话,我居然有我也是“国宝”的错觉,哈哈哈,突然有点厚脸皮了,谁让这家伙那么可爱,第一天就把市区能去的都逛了个七七八八,到底是 3 个年轻人,身体真好,哎,都没有停下来好好看看成都的美女帅哥,都光顾着吃喝和看风景了,话不多说,先来几张图片缓和下气氛,写到这里我又想起了蛋烘糕,想吃,想起了又辣又麻的椒麻鸡,想吃,想起了兔头(真是让人秃头,兔兔那么可爱,为啥要吃兔兔,其实我更喜欢兔子肉,哈哈哈),想吃。啊好难受,都想吃,我觉得我如果待在这个城市,一定能长胖
-
-
-
-
-
-皂片有点多,就放这几张吧,看看就好,后面两天是不是下点小雨,阴蒙蒙的天气就和我当时的心情对应上了,因为我还要修改问题,前面留下的坑,还要去填,真的,真的,真的我不是故意要把工作的事情带到旅途中来的,下次我一定不带电脑了,打死不带,刀架到我脖子也不带。让某位同学一整天都在家里刷剧,真是罪过罪过(虽然你曾经这里待了很久,尝遍了这里美食,看尽了这里的美景,但这不一样,因为和你同看风景,同品美食的是不同的人,不同的心情)

-

话说回来玉林街道那里好吃的真多,那个谁自己要控制住,不然想瘦真的有点难,话说回来在成都的 3 天虽然有亿点点的不完美,但是正是由于这些不完美,才让我的记忆更加深刻。按照网上所谓的高评分美食,走路老远的路过去,发现吃完并没有想想中那么好吃(我严重怀疑他们是刷单刷出来的);不看清目的地营业时间,跑去傻傻的在门口不知道该往哪去(告诫我下次做规划这种事情一定提前要有备用计划);一起搭公交车去很远的地方买甜点,虽然有点小远,但是值得。

-

不知道下次会是什么时候再去成都了,也不知道下次身边会是谁,能不能像现在一样能聚在一起。我想的可真远,大白天发什么神经,还是过好眼前的,得嘞……珍惜现在拥有的

-

三天后,一行三人出发,前往西藏拉萨。我们并没有搭乘火车,而是选择了飞机,谁让这价格那么香,还能节省接近 2 天的时间。抵达拉萨后,并没有出现不良的反应

-

日光之城 - 拉萨

-

引入眼帘给我的第一感觉是蓝,蓝的彻彻底底,没加半点人工滤镜渲染,也没 AI 换天,这里随手拍照都能拍到非常好看的图片,不需要修图,这里的云也很配合,像是一种出淤泥而不染的干净,在蓝色背景的映衬下,云朵就像是在你的手边随手都可以去摘下来捧在手里,话不多说,我们先来看拉萨的牌面,大气恢弘的建筑俯瞰整个拉萨市(标准的游客拍照)
-
-第一天就头疼的么,不存在的,这里海拔也不过 3600 左右,还是能接受的,只不过快步走或跑步是真的比内陆容易喘气。在高原地带由于氧气稀薄,汽车大部分的燃油都没有充分燃烧,尾气直接排进空气,这种味道会让你欲仙欲死。四天的行程已提前被安排,跟着导游,听他安排

-

第二天一大早就集结,开始 4 天的行程,行程结束后再回到拉萨市区,迎着朝阳汇聚了来自不同地方 12 个小伙伴,接下来的这几天我们这些人就是一个小群体,彼此互相照顾,所幸我们所在的团队中年龄都不大,都是年轻小伙和年轻姑娘,大家之间也没什么隔阂,很快就能玩耍到一块去,下图是 07:35 这个点,这里的天还没有完全亮
-

-

我们行程的这几天天气非常好,都是蓝天白云,刚开始不停的按下快门,想要记录下这让人心旷神怡的美景,生怕自己错过了最美的时刻,但到后来都有些麻木了,原因是你只要抬头就能看到,根本拍不过来,索性就好好沉浸其中,好好呼吸这有点甘甜的空气
-
-

-

当蓝天配上纳木错的美景,是一种秀色可餐的满足感
-
-

-

当蓝天被雄鹰所占领,这就是鹰击长空现实版
-

-

当所有的疲惫和不适到了珠峰大本营,都会被抛之脑后,因为我已深深被这傲视群雄的孤独所吸引,第一次与世界最最高峰是这么的近
-

-
-

照片实在是有点多,继续挖坑后面整理成相册再放出来

-
-

上一次超过 30H 的行程,那会刚毕业,一路上总能听到各式各样千奇百怪的故事,和遇到一些形形色色的路人,因为那时候,在火车上经常没有信号,大家会一起唠唠嗑,我就听着这样乱七八糟的故事和别人经历的事,不想听了就翻开自己带的书,就这样度过那漫长的行程。而现在网络普及,信号增强了,但是人与人的交流好像都被转移到线上了,在一块面对面交流的人少了,故事也都来源于那块屏幕,隔着屏幕说话,科技的初衷是提高我们的生活质量和方便我们的生活,而不是隔断人与人的面对面交流

-

总结

-

-
-

有形的东西迟早会凋零,但只有回忆是永远不会凋零的

- -
-
    -
  1. 好好学一下摄影和构图吧,总会用得上
  2. -
  3. 要把快乐传染给周围的人
  4. -
  5. 有话就要说,别一个人闷在心里
  6. -
  7. 把想到的感受到的及时记录下来,有时候灵感,想法就是那么昙花一现
  8. -
  9. 多吃点肉吧,不然风一吹朋友就找不到我了
  10. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/11/08/microservices-springcloud1/index.html b/2020/11/08/microservices-springcloud1/index.html deleted file mode 100644 index e9a7a0eb5..000000000 --- a/2020/11/08/microservices-springcloud1/index.html +++ /dev/null @@ -1,517 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -微服务架构 - SpringCloud 生态整合(一) | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 微服务架构 - SpringCloud 生态整合(一) -

- - -
- - - - -

微服务这一概念在 2014 年的 3 月份随着 James Lewis 和 Martin Fowler 在博客中对于微服务这一概念做出了详细的阐述,开始走进开发者的视野。在 Spring 官方的加持下,助推微服务架构风格的应用开始火边整个后端领域。在早起微服务化的演进中 Netflix 的一些列开源的组件,迅速占领微服务生态中的 C 位,提供了网关路由,负载均衡,服务注册发现,服务通信,服务熔断限流等核心组件

- -

本系列文章是基于 Spring 官方提供的组件,完成相关功能组件的整合,以满足企业需求为目的的学习总结。本篇文章先从 Spring 官网经典的微服务架构图开始

-

-

注意这里主要以 Spring 系官方相关的开源组件为基础构建,并非是 Spring Cloud 项目的照搬,而是基于企业实际业务需求的抽象整合,只为提高效率、总结编程套路以及提升编程思想

-
-

当然这只是我在实际生产实践中的总结,并不一定适合你的业务场景,你也可以参考我的另一个系列 Alibaba 生态整合,希望能对你有所帮助

-

选型

-
    -
  • 编程语言:Oracle JDK 8
  • -
  • 构建工具:Gradle
  • -
  • 网关路由:Spring Cloud Gateway
  • -
  • 服务通信:OpenFeign
  • -
  • 注册中心:Eureka
  • -
  • 配置中心:Spring Cloud Config
  • -
  • 限流,熔断,降级:Hystrix
  • -
  • 文档管理:SpringFox + Knife4j
  • -
  • 部署发布:Docker + Nexus Repository OSS
  • -
  • 链路追踪:Spring Cloud Sleuth + Zipkin
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/11/10/mq-rabbit1/index.html b/2020/11/10/mq-rabbit1/index.html deleted file mode 100644 index e5dc04029..000000000 --- a/2020/11/10/mq-rabbit1/index.html +++ /dev/null @@ -1,490 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -MQ 系列 — RabbitMQ(一)环境搭建 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- MQ 系列 — RabbitMQ(一)环境搭建 -

- - -
- - - - -
-
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/11/10/mq-rocket1/index.html b/2020/11/10/mq-rocket1/index.html deleted file mode 100644 index 718563642..000000000 --- a/2020/11/10/mq-rocket1/index.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -MQ 系列 — RocketMQ(一)环境搭建 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- MQ 系列 — RocketMQ(一)环境搭建 -

- - -
- - - - -

本篇我们来看 MQ 系列的另一个广泛使用的中间件 RocketMQ。官方介绍到 “Apache RocketMQ™ 是一个统一的消息传递引擎,轻量级的数据处理平台。Apache RocketMQ 是一个分布式消息传递和流媒体平台,具有低延迟,高性能和可靠性,万亿级容量和灵活的可伸缩性” 。更重要的是在分布式消息队列中,目前唯一提供完整的事务消息的,只有 RocketMQ。

- -
    -
  • RocketMQ 3.0.8 以及之前的版本是 支持分布式事务消息(找不到对应的提交记录)
  • -
  • RocketMQ 3.0.8 之后,分布式事务的阉割了,不支持分布式事务消息(找不到对应的提交记录)
  • -
  • RocketMQ 4.0.0 开始 Apache 孵化,但是也不支持分布式事务消息
  • -
  • RocketMQ 4.3.0 又开始支持分布式事务消息
  • -
-
-

基本概念

-

RocketMQ 由四部分组成:name servers, brokers, producers and consumers。它们中的每一个都可以在没有单个故障点的情况下进行水平扩展

-

name servers

-

用来保存 Broker 相关 Topic 等元信息并给 Producer,提供 Consumer 查找 Broker 信息。主要包括两个功能:

-
    -
  1. Broker 管理,NameServer 接受来自经纪人群集的注册,并提供心跳机制以检查经纪人是否还活着
  2. -
  3. Routing 管理,每个NameServer 将保存有关代理群集的完整路由信息以及客户端查询的队列信息
  4. -
-

brokers

-

负责消息的存储和传递,消息查询,HA 保证等(消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息)。Broker 服务器具有几个重要的子模块:

-
    -
  • Remoting Module:处理来自客户端的请求
  • -
  • Client Manager:管理客户(生产者/消费者)并维护消费者的主题订阅
  • -
  • Store Service:提供简单的 API,以在物理磁盘中存储或查询消息
  • -
  • HA Service:提供主代理(master broker)和从代理(slave broker)之间的数据同步功能
  • -
  • Index Service:通过指定的键为消息建立索引并提供快速的消息查询
  • -
-

producers

-

负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。支持分布式部署,分布式生产者通过多种负载平衡模式将消息发送到 Broker 集群。发送过程支持快速失败并且延迟低

-

consumers

-

负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序。支持 “推和拉” 模型中的分布式部署。它还支持集群使用和消息广播。它提供了实时消息订阅机制,可以满足大多数消费者的需求

-

整体流程

-

准备工作

-
    -
  • Linux
  • -
  • JDK8+
  • -
  • Maven3.2.x+
  • -
  • Git
  • -
-
-

相关工具没安装可参考 Linux 常用应用安装

-
-

单机部署

-

单机部署,主要是进行 RocketMQ 的简单使用,因此没有必要分配较大内存空间,RocketMQ NameServer 默认会占用 4G,因此在启动部署时会调整 JVM 的相关参数,指定分配内存空间

-

普通部署

-

RocketMQ 部署

-
    -
  1. Nameserver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 程序存放位置,根据喜好
    cd /home/application
    # 下载应用
    wget https://archive.apache.org/dist/rocketmq/4.7.1/rocketmq-all-4.7.1-bin-release.zip
    # 解压文件,并进入解压后的目录,进行查看目录概要等信息(没有 unzip 命令,请 yum install unzip)
    unzip rocketmq-all-4.7.1-bin-release.zip && cd rocketmq-all-4.7.1-bin-release/ && ls -l
    # 进入启动目录
    cd bin/

    # 编辑启动脚本文件,修个相应的 JVM 参数
    vim runserver.sh
    ### 定位到: JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=1 28m -XX:MaxMetaspaceSize=320m"
    ### 更改为: JAVA_OPT="${JAVA_OPT} -server -Xms512M -Xmx512M -Xmn256M -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"

    # 修个完成后启动 nameserver 应用
    nohup ./mqnamesrv &
    -
  2. -
  3. 启动 broker
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 进入 bin 目录
    cd /home/application/rocketmq-all-4.7.1-bin-release/bin/

    # 编辑启动脚本文件,修个相应的 JVM 参数
    vim runbroker.sh
    ### 定位到: JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
    ### 更改为: JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn512m"

    # 修个完成后,后台启动 broker,-n 指定 NameServer 服务ip地址
    nohup ./mqbroker -n localhost:9876 &
    -
  4. -
  5. 验证 RocketMQ
    1
    2
    # 使用 clusterList 命令来查看集群的状态
    sh /home/application/rocketmq-all-4.7.1-bin-release/bin/mqadmin clusterList -n 127.0.0.1:9876
    -
  6. -
-

RocketMQ-Console 部署

-

通过命令去操作 RocketMQ,其实是比较麻烦,没有图形化来的直观和方法。为此 RocketMQ 官方提供了一个运维管理界面 RokcetMQ-Console-Ng,用于对 RocketMQ 集群提供常用的运维功能

-
-

基于 SpringBoot 开发

-
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
wget https://github.com/apache/rocketmq-externals/archive/rocketmq-console-1.0.0.tar.gz
tar -xf rocketmq-console-1.0.0.tar.gz
# 重命名,为了方便后续操作
mv rocketmq-externals-rocketmq-console-1.0.0/rocketmq-console rocketmq-consoe
cd rocketmq-console

# 编辑配置文件
vim src/main/resources/applications.properties
### 修改指向的 nameserver 地址
### rocketmq.config.namesrvAddr=127.0.0.1:9876

# 使用 maven 命令编译源代码
mvn clean package -DskipTests
# 复制包到自己常用的软件安装目录
cp rocketmq-console-ng-1.0.0.jar /opt/application/
# 启动 rocketmq-conolse
nohup java -jar rocketmq-console-ng-1.0.0.jar &
-

正常启动后,访问:http://localhost:8080 查看是否安装成功

-

如果你使用的 root 用户启动 rocketmq, rocketmq-console 应用,那么他们的日志分别在

-
    -
  • rocketmq: /home/root/logs/rocketmqlogs/
  • -
  • rocketmq-console: /home/root/logs/consolelogs
  • -
-
-

Docker 部署

-

截止 2020-11-10,官方的镜像依然还是 4.6 版本,难道又是阿里没人维护的 KPI 🙄

-

RocketMQ-Docker

-

分布式部署

-

普通部署

-

Docker 部署

-

参考

-
    -
  1. 《Apache RocketMQ 从入门到实战》.pdf
  2. -
  3. 芋道 RocketMQ 极简入门
  4. -
  5. 芋道 Spring Boot 消息队列 RocketMQ 入门
  6. -
  7. RocketMQ 4.7.1 环境搭建、集群、SpringBoot整合MQ
  8. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/11/11/microservices-alibaba1/index.html b/2020/11/11/microservices-alibaba1/index.html deleted file mode 100644 index ee357407f..000000000 --- a/2020/11/11/microservices-alibaba1/index.html +++ /dev/null @@ -1,713 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -微服务架构 - Alibaba 生态整合(一) | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 微服务架构 - Alibaba 生态整合(一) -

- - -
- - - - -

曾几何时,市面上对于微服务,分两个派系,一个派系以阿里为主的 Dubbo 生态体系,还有一派以 Spring Cloud 生态为主的体系,这两个系列的讨论也一直没有停息过。但现在 Spring Cloud Alibaba 的出现,提供了一整套构建分布式应用开发的微服务组件,由于这些组件是构建在原生的 Spring Cloud 之上,因此其服务治理方面的能力可认为是 Spring Cloud Plus, 不仅完全覆盖 Spring Cloud 原生特性,而且提供更为稳定和成熟的实现。那么从本系列就开始跟着我一起用阿里系的应用搭建分布式微服务应用,满足企业级的应用需要,而不是停留在 Dome 级别的应用框架使用。废话不多说,我们一起开始这一系列的实践

- -

本篇文章主要讲一讲在构建分布式微服务应用时,经常遇到的问题以及对于同类型组件选择,以及在开发过程中相关问题的思考,对于在整个应用开发过程中,开发人员应该怎么去配合等等,那第一个问题是面对我们的业务场景该如何去做技术选型,我们先看 Spring 官方经典的微服务架构图

-

-

微服务的核心组件由:网关,服务注册发现,服务配置,熔断限流等组成

-

注意这里微服务主要以 Alibaba 系相关的开源组件为基础构建,并非是 Spring Cloud Alibaba 项目的照搬,而是基于企业实际业务需求的抽象整合,只为提高效率、总结编程套路以及提升编程思想

-
-

选型

-
    -
  • 编程语言:Oracle JDK 8
  • -
  • 构建工具:Gradle
  • -
  • 网关路由:Spring Cloud Gateway
  • -
  • 服务通信:Dubbo
  • -
  • 消息管理:RockerMQ
  • -
  • 分布式事务:Seata
  • -
  • 注册中心及配置中心:Nacos
  • -
  • 限流,熔断,降级:Sentinel
  • -
  • 文档管理:SpringFox + Knife4j + Dubbo-Api-Docs
  • -
  • 部署发布:Docker + Nexus Repository OSS
  • -
  • 运维监控:Prometheus + Grafana
  • -
-

-

SpringCloud VS SpringCloud Alibaba

-

这里我汇总到表格中,方便查看比较

-

-

相关问题

-

JDK

-

OpenJDK

-

Java 最早由 SUN(Sun Microsystems,发起于美国斯坦福大学,SUN 是 Stanford University Network 的缩写)发明,2006 年 SUN 公司将 Java 开源,此时的 JDK 即为 OpenJDK

-

OpenJDK 是 Java SE 的开源实现,由 SUN 和 Java 社区提供支持,2009 年 Oracle 收购了 SUN 公司,自此 Java 的维护方之一的 SUN 也就变成了 Oracle

-

大多数 JDK 都是在 OpenJDK 的基础上编写实现的,比如 IBM J9,Azul Zulu,Azul Zing 和 Oracle JDK。几乎所有的 JDK 都派生自 OpenJDK,他们之间不同的是授权许可证。常见的 OpenJDK 发行商

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
发行商长期支持(TLS)许可证(license)TCK 测试未修改的上游构建提供商业支持
AdoptOpenJDKYesYesNoOptionalOptional(IBM)
Alibaba DragonwellYesYesYesNoNo
Amazon CorrettoYesYesYesNoOptional
(on AWS)
Azul ZuluYesYesYesNoOptional
BellSoft Liberica JDKYesYesYesNoOptional
IBM Java JDKYesNoYesNoYes
ojdkbuildYesYesNoYesNo
OpenLogic OpenJDKYesYesNoNoOptional
Oracle Java SEYesNoYesNoYes
Oracle OpenJDKNoYesYesYesNo
Red Hat OpenJDKYesYesYesNoYes
SAP SAPMachineYesYesYesNoNo
-
-

TLS:long-term support,长期支持(LTS)是一种产品生命周期管理策略,在该策略中,与标准版相比,计算机软件的稳定版本可以维持更长的时间。该术语通常保留给开源软件,它描述的软件版本比该软件的标准版本支持数月或数年的支持。
-TCK:Technology Compatibility Kit,技术兼容性套件(TCK)是一套测试套件,至少名义上检查Java规范请求(JSR)的特定声称实施是否符合要求

-
-

OralceJDK

-

显而易见 OracleJDK 是在 Oracle 收购 SUN 公司之后,基于 OpenJDK 源码构建的 JDK 被命名了 OracleJDK,两则之间没有重大的技术差异

-

两者的区别

-

问题

-

有人会说了,这有啥好说的,我们在公司开发都是用 OracleJDK 的。曾经我也以为这两个区别不是很大,看公司的使用情况了,直到我使用了 CentOS 7 系统默认带的 OpenJDK 来编译 Gradle 项目,死活是编译不过,总是提醒我找不到 tools.jar 包。有图有真相

-

-

一开始,我把以为是环境配置的问题,但是经过一番折腾,卸载了自带的 OpenJDK,然后再用 yum install java 命令去安装 OpenJDK,发现并不是环境的问题,而是系统自带的这个 OpenJDK 是 JRE,所以并没有包含 tools.jar 文件。所以这个问题就是你系统 JDK 的问题了。建议卸载 JRE,重新安装 JDK

-
1
2
3
4
# 可以先查找 JDK,下面命令是我查找 java-1.8 的相关应用
yum search java-1.8 | grep -i --color JDK
# 也可以直接安装 JDK,比如我这里提供的 java-1.8.0-openjdk-devel.x86_64
yum install java-1.8.0-openjdk-devel.x86_64
-

-

Gradle or Maven

-

关于如何使用 Gradle 构建项目,以及使用 Gradle 配置符合企业敏捷开发需求,可查看我的 Gradle 系列的文章

-

jar 与 bootJar

-

之前在《SpringBoot(二) 启动分析JarLauncher》文章中进行对 SpringBoot 应用启动做了分析,提到了 jar 规范,做了简单的介绍,那么本篇在此基础上进一步的完善这个知识点

-
-

这里以 rc-microservices-alibaba 项目的 microservices-alibaba-gateway 模块的编译为例

-
-

jar

-

jar(Java Archive)可以看做是特殊文件压缩的一种,通常用于聚合大量的 Java 类文件,相关的元数据和资源文件到一个文件,以便分发 Java 平台应用软件或库。jar 文件是一种归档文件,以 ZIP 格式构建,以 .jar 为文件扩展名。包含一个可选的 META-INF 目录,可以通过命令行 jar 工具或使用 Java 平台中的 java.util.jar API 创建 jar 文件

-

可以看到,我们打包成 jar 的文件,仅仅是源码+资源文件,以及生成的 META_INF 文件

-
1
2
3
4
5
6
7
8
9
10
microservices-alibaba-gateway-1.0-SNAPSHOT
├── META-INF
├── org
│ └── incoder
│ └── gateway
│ ├── config
│ ├── exception
│ └── filter
├── static
└── templates
-

bootJar

-

看名字就知道,这是 SpringBoot 的专属 jar。为什么会有这种 jar,原因是在 SpringBoot 出现之前,我们的 jar 应用想要运行,需要将应用放入到 Tomcat 中。而 SpringBoot 的出现改变了这层关系,是 SpringBoot 在打包成 bootJar 时,会内置 Tomcat,我们可以直接运行启动 jar 应用,可能有人会说,这怎么改变了,不都还是运行在 Tomcat 上么。没错它确实依然运行在 Tomcat 上,但是他们的加载方式改变了

-

我们可以看到,打成 bootJar 的文件,除了 META-INF 相关文件,并且包含了 BOOT-INF 的 lib 路径下存放项目所使用的所有第三方的 jar 包 ,同时在打包的根目录,生成了 SpringBoot 的 loader 相关的文件

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
microservices-alibaba-gateway-1.0-SNAPSHOT
├── BOOT-INF
│ ├── classes
│ │ ├── META-INF
│ │ ├── org
│ │ │ └── incoder
│ │ │ └── gateway
│ │ │ ├── config
│ │ │ ├── exception
│ │ │ └── filter
│ │ ├── static
│ │ └── templates
│ └── lib
├── META-INF
└── org
└── springframework
└── boot
└── loader
├── archive
├── data
├── jar
├── jarmode
└── util
-

Spring 生态

-
    -
  1. Spring:一个一站式轻量级Java 开发框架,核心是控制反转(IOC)和面向切面(AOP),针对开发 Web 层,业务层,持久层等提供了多种配置解决方案,也是整个微服务开发的基石
  2. -
  3. SpringMVC:是 Spring 基础之上的一个 MVC 框架,主要处理 Web 开发的路径映射和视图渲染,属于 Spring 框架中 Web 层开发的一部分(开发配置非常繁琐,复杂)
  4. -
  5. SpringBoot:专注于服务方面的接口开发,和前端解耦,默认优于配置,一定程度上取消了 XML 配置,是一套快速开发的脚手架,能快速开发单个微服务
  6. -
  7. SpringCloud:大部分功能组件基于 SpringBoot 去实现,提供了完整的微服务架构的技术生态,SpringCloud 专注于微服务的整合和管理
  8. -
-

单工程 or 聚合工程

-
-

个人推荐单工程的方式,毕竟聚合工程最终会随着业务的发展推进,需要拆分为单项目开发管理,那还不如一开始就拆分

-
-

单工程

-

这里的单工程是指,每一个模块都是一个项目,由一个仓库进行管理,特点及要求如下

-
    -
  1. 适合团队小组分工明确,开发人员多
  2. -
  3. 适合项目迭代快
  4. -
  5. 需要比较健全的基础设施,比如网关,公共基础工具包,消息管理,以及自动化部署相关服务设施
  6. -
-
-

相关的构架过程可参考 Gradle(三)SpringBoot 单工程 文章

-
-

聚合工程

-

这里的聚合工程是指,将整个系统开发的所有模块以及公共模块都放在一个项目工程中,也就是用同一个仓库来进行管理,特点如下

-
    -
  1. 适合项目初期,项目分工不是特别明确,开发人员少
  2. -
  3. 项目需要集中管理
  4. -
-
-

相关的构架过程可参考 Gradle(四)SpringBoot 聚合工程 文章

-
-

业务拆分

-

对于服务的拆分是没有统一的标准,除了通过实际的业务场景,团队能力,人员组织架构等多种因素综合考虑。都根据实际的需求进行调整,对于拆分主要从以下原则去思考

-
    -
  1. 单一职责原则:保证每个服务只做好一件事,体现“高内聚,低耦合”,尽量减少对外界环境的依赖
  2. -
  3. 服务依赖原则:避免服务间的循环依赖,在设计时就需要对服务进行分级,区分核心服务与非核心服务
  4. -
  5. Two Pizza Team原则:让团队保持在2 个披萨就能让队员吃饱的小规模概念
  6. -
-

参考

-
    -
  • 传统行业转型微服务的挖坑与填坑
  • -
  • Java官方(Oracle/Sun)发布的JDK,和开源项目OpenJDK,里面包含的JVM是否相同
  • -
  • OpenJDK和Oracle JDK有什么区别和联系?
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/11/13/mac-question/index.html b/2020/11/13/mac-question/index.html deleted file mode 100644 index f8d5e1a59..000000000 --- a/2020/11/13/mac-question/index.html +++ /dev/null @@ -1,513 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -MacBook Pro 疑难杂症 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- MacBook Pro 疑难杂症 -

- - -
- - - - -

这是一篇记录使用macOS系统时遇到的一些疑难杂症

-

macOS Big Sur

-

在 2020.11.13 正式推送了 macOS Big Sur version 11.0.1 版本,这一个版本是改动比较大的版本,这里关于它的新特性就不做介绍了,有兴趣的请查看官方网站介绍 Big Sur

-

- -

Glance 失效

-

Glance 是一个快速预览增强,可以对一些文件进行快速预览,大大提高我们的日常效率,但该应用在 Big Sur 版本中不兼容,由于作者已入职 Apple,且对项目做了归档,不在维护,因此该问题依旧没有解决,可以使用一个付费的应用iPreView来满足当前需要

-
-

Glance 在 Big Sur 系统中失效

-
-

AirPods 异常

-

在 AirPods 使用过程中,发现有时候耳机并不能正常工作。通常情况下,我会断开与 macOS 的连接,重新连接,如果还是不能正常工作,在 macOS 的系统蓝牙设置里面,移除连接的耳机设备,将耳机放入 AirPods 盒子里面,先盖上盒子,然后再打开盒子,此时并按住 AirPods 盒子背后的按钮,直到前面呼吸灯变成白色,然后再 macOS 的蓝牙里面找到新的设备,并连接配对。同时也可参考官方指引步骤 连接并使用 AirPods 和 AirPods Pro

-

单耳工作

-

换一个连接设备,检查耳机是否正常,如果是正常,那说明耳机没有问题,问题就出在 macOS 声音管理上面,打开系统设置 -> 声音 -> 输出模式 ->设置为居中的平衡模式(既双耳工作)

-

airpods-settings

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/11/15/interview-2020/index.html b/2020/11/15/interview-2020/index.html deleted file mode 100644 index 5bd9f5a99..000000000 --- a/2020/11/15/interview-2020/index.html +++ /dev/null @@ -1,505 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -2020 年秋季面试经历 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 2020 年秋季面试经历 -

- - -
- - - - -
- -
-
- - -
-
-
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/11/20/hexo-advanced/index.html b/2020/11/20/hexo-advanced/index.html deleted file mode 100644 index 1eb219599..000000000 --- a/2020/11/20/hexo-advanced/index.html +++ /dev/null @@ -1,613 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Hexo Blog 高级指南 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Hexo Blog 高级指南 -

- - -
- - - - -

NexTHexo 非常受欢迎的博客主题,方便简洁,但却不简单的功能,你可以在提供的强大功能基础上进行扩展或者自定义,来满足你的个性化需求。本篇文章主要是对应 NexT 提供的一些高级功能的使用,作为一个持续更新的文章吧,记录自己 SX 操作,当然也是我平时在使用 NexT 时遇到的一些问题的记录。好了废话不多说了,我们直接进入正题

- -

博客升级

-

每次对于 NexT 的升级或多或少都会遇到些问题,这次也不例外,首先是对于不同版本的管理,由于一些历史原因有三个组织仓库分别对应不同的版本域,升级是需要注意下,本次我是从 7.8.0 版本升级到 8.0.x 版本,以后跟随官方,每月更新 NexT

-

npm 改成 yarn(可选)

-
-

yarn 的安装,请自行根据你的系统去安装,我这里 macOS 使用命令即可 brew install yarn

-
-
    -
  1. 删除根目录的 package-lock.json,并在根目录执行 hexo clean && rm -rf node_modules/
  2. -
  3. 根目录下执行 yarn install
  4. -
-

更改 NexT 主题仓库

-
    -
  1. 删除当前主题,在根目录下执行 rm -rf themes/
  2. -
  3. 安装新的主题, -
      -
    • 方案一:在根目录下执行命令添加主题
    • -
    -
    1
    git clone https://github.com/next-theme/hexo-theme-next themes/next
    -
      -
    • 方案二:通过 yarn 来管理主题
    • -
    -
    1
    yarn add hexo-theme-next
    -
  4. -
-

修改配置

-

之前为了使主题更新不受影响,在项目的根目录 source/_data 路径下有一个 next.yml 文件来进行对 NexT 的自定义设置,那么在 8.0 版本开始,在项目根目录 _config.{theme}.yml 文件来代替之前在 source/_data 路径下的 next.yml 文件

-

问题

-

node --trace-warnings

-
异常信息
-

由于 NexT 需要 Hexo5.0+,在升级到 NexT 8.0.x 版本警告信息如下

-
1
2
3
4
5
6
7
(node:17336) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
(node:17336) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
-
原因分析
-

是由于 Hexo 项目嵌套依赖了 stylus 包,而对于 0.54.5 版本在 Node 14+ 版本存在问题,比如这里 hexo-renderer-stylus 包的依赖

-
1
2
3
4
5
6
7
……
│ │
├─┬ hexo-renderer-stylus@2.0.1
│ ├─┬ nib@1.1.2
│ │ └─┬ stylus@0.54.5
│ │ │
……
-
解决方法
-
    -
  1. 可以降低你的 Node 版本到 12 版本
    1
    2
    3
    brew uninstall node
    brew install node@12
    brew link --overwrite --force node@12
    -
  2. -
  3. 推荐,更改替换 stylus 版本,在你的 package.json 文件中,添加如下配置
    1
    2
    3
    "resolutions": {
    "stylus": "^0.54.8"
    }
    -
  4. -
-
总结
-

🌀 pull-2538
-🐞 issues-2534
-🛠 solve-Accessing non-existent property

-

hexo-douban

-

之前用了 hexo-douban 插件来进行对 books 和 movies 进行管理,在升级到 Node 14+版本上,当前的插件也停止工作了,异常日志如下

-
1
2
3
INFO  0 books have been loaded in 1130 ms, because you are offline or your network is bad
INFO 0 movies have been loaded in 1329 ms, because you are offline or your network is bad
INFO 0 games have been loaded in 1004 ms, because you are offline or your network is bad
-

作者在🐞 issues-2534 做了回复,暂时没有替代方案,故在新版中,我停止了 hexo-douban 插件的使用,挖个坑,等自己有时间或者有人修复此问题再或者有替代插件后再重新启用

-
    -
  1. 移除 hexo-douban 插件
    1
    2
    3
    4
    # yarn
    yarn remove hexo-douban
    # npm
    npm uninstall hexo-douban
    -
  2. -
  3. 移除 _config.yml 配置文件中,douban 的相关的配置
  4. -
  5. 移除 _config.{theme}.yml 配置文件中,menu 配置的站点入口设置
  6. -
-

博客评论

-

在 NexT version 8.1.0 版本,由于安全问题,Valine被移除暂时我并未迁移 Valine 的评论

-

博客已启用 utterances 评论支持,配置也比较简单,如下

-
1
2
3
4
5
6
7
utterances:
enable: true
repo: BladeCode/BladeCode.github.io # Github repository name
# Available values: pathname | url | title | og:title
issue_term: title
# Available values: github-light | github-dark | preferred-color-scheme | github-dark-orange | icy-dark | dark-blue | photon-dark | boxy-light
theme: github-light
-

文章加密

-

对于 NexT 的文章,有时需要进行加密访问,那么该怎么去处理呢,其实这一点在 NexT 的生态里已经有了这样的插件,我们可以直接在使用在我们的 NexT 里面,只需要简单的配置

-
1
2
3
4
# npm 
npm i hexo-blog-encrypt --save
# yarn
yarn add hexo-blog-encrypt
-

加密优先级:文章信息头 > 按标签加密

-

站点配置(_config.yml)

-

简单配置

-
1
2
3
# 文章密码访问 hexo-blog-encrypt
encrypt:
enable: true
-

更多配置

-

可以对一类(标签)来进行统一的密码设置

-
1
2
3
4
5
6
7
8
9
10
# 文章密码访问 hexo-blog-encrypt
encrypt:
abstract: 有东西被加密了, 请输入密码查看.
message: 您好, 这里需要密码.
tags:
- {name: tagNameA, password: 密码A}
- {name: tagNameB, password: 密码B}
template: <div id="hexo-blog-encrypt" data-wpm="{{hbeWrongPassMessage}}" data-whm="{{hbeWrongHashMessage}}"><div class="hbe-input-container"><input type="password" id="hbePass" placeholder="{{hbeMessage}}" /><label>{{hbeMessage}}</label><div class="bottom-line"></div></div><script id="hbeData" type="hbeData" data-hmacdigest="{{hbeHmacDigest}}">{{hbeEncryptedData}}</script></div>
wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.
-

单文章配置

-

在你需要加密的文章前面,根据需要添加对应的参数,这里仅是一个示例

-
1
2
3
4
5
6
7
8
9
10
11
---
title: Hello World
tags:
- 加密文章tag
date: 2020-11-20 18:18:18
password: helloworld
abstract: 该文章已加密, 请输入密码查看。
message: 该文章已加密, 请输入密码查看。
wrong_pass_message: 密码不正确,请重新输入!
wrong_hash_message: 文章不能被校验, 不过您还是能看看解密后的内容!
---
-

各参数说明

-
    -
  • password:文章密码
  • -
  • abstract:文章摘要,会显示在博客的列表页
  • -
  • message:文章查看时,密码输入框上面的描述性文字
  • -
  • wrong_pass_message:校验失败提示
  • -
  • wrong_hash_message:hash 验证失败
  • -
-

多语言

-

对于多语言,根据自身需要添加,默认,修改博客项目根目录 _connfig.yml 文件 language 属性即可

-
    -
  1. 对于单语言:language: xxx(具体语言可查看下方的官方说明)
  2. -
  3. 对于多语言: -
      -
    • 语言添加
      1
      2
      3
      language:
      - zh-CN
      - en
      -
    • -
    • 更改语言切换,_config.{theme}.yml 文件,language_switcher设置为 true
    • -
    -
  4. -
  5. 字段定义,如果一些字段的翻译不是你想要的,你可以自行修改 -
      -
    • 在根目录的 source/_data 文件夹下,创建 languages.yml 文件
    • -
    • 在文件中,修改对应语言的字段
      1
      2
      3
      4
      5
      6
      7
      8
      9
      zh-CN:
      # items
      post:
      copyright:
      # the translation you perfer
      author: 本文博主
      en:
      menu:
      schedule: Calendar
      -
    • -
    -
  6. -
-
-

多语言配置

-
-

GitHub Action

-

Hexo PWA

-
-

由于暂未支持 Hexo5.0+版本,先占坑

-
-

参考

-
    -
  • 更新说明及常见问题
  • -
  • 将 Hexo 升级到 v5.0.0
  • -
  • 用 GitHub Actions 来自动部署 Hexo
  • -
  • Hexo博客部署PWA
  • -
  • 博客完美支持 PWA
  • -
  • 三步,让 Hexo 轻松支持 PWA
  • -
  • Pwabuilder
  • -
  • Hexo 相关问题和优化
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/11/25/open-license/index.html b/2020/11/25/open-license/index.html deleted file mode 100644 index 83ef3c33f..000000000 --- a/2020/11/25/open-license/index.html +++ /dev/null @@ -1,536 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -开源协议,该如何选择 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 开源协议,该如何选择 -

- - -
- - - - -

现如今软件行业的发展完全离不开开源社区,很多优秀的软件应用、技术都能看到开源软件的影子,我们都是站在巨人的肩膀上。对于软件行业的从业者,能为开源项目贡献自己的力量,或是将自己对某一个细分领域所做的研究实践开源出来,这是一件非常值得骄傲的事情。而要参与一个大型的开源项目,你除了需要该项目涉及的核心技术知识外,还需要了解一定的开源项目运转方式等,对于如何参与开源项目,这里暂不做过多的介绍,有兴趣的可以移步 Gitee 发起的《开源指北》项目,该项目中详细介绍了如何参与开源项目。本篇文章也不啰嗦这一点,仅仅围绕开源协议,我们应该清楚的常识和注意的点

- -

在软件开发中通常有两种情况我们需要考虑软件的开源协议或者使用协议

-
    -
  1. 我们需要使用到业界的一些优秀的软件包来提高我们开发的效率,避免了重复早轮子,所选择的这些软件包我们不但考虑功能的同时,也要考虑软件包的授权协议
  2. -
  3. 我们需要将自己的经验或者软件产品需要开源时,为了保护自己的权益,我们也需要选择一个合适的开源协议
  4. -
-

我这里还是引用比较经典 阮一峰 文章中所绘制关于如何选择开源协议的图
-
-图中已经很清楚的表示了如何去选择 LGPL, Mozilla, GPL, BSD, MIT, Apache 这 6 种协议

-

常用协议

-

这里我们通过表格的形式介绍下这 6 种协议,当然除了表中列出的这些协议之外还有很多协议,我们就挨个来简单对他们有一个了解和认识

-

-

其他协议

-

BY-NC-SA

-

你会发现每篇文章下面都有申明版权,这里使用的是 BY-NC-SA 4.0 的协议,他们的含义如下

-
    -
  • :知识共享(CreativeCommons)
  • -
  • NC:非商业性使用(NonCommercial),您不得将本作品用于商业目的
  • -
  • SA:相同方式共享(ShareAlike),如果您再混合、转换或者基于本作品进行创作,您必须基于与原先许可协议相同的许可协议 分发您贡献的作品
  • -
-

使用此协议,您可以自由地

-
    -
  1. 共享 — 在任何媒介以任何形式复制、发行本作品
  2. -
  3. 演绎 — 修改、转换或以本作品为基础进行创作
  4. -
-
-

只要你遵守许可协议条款,许可人就无法收回你的这些权利

-
-

选择

-

上面说了那么多,有些协议并没有展开来说可能并不适用你当前的所需要选择的协议,那么你可以根据实际情况去筛选,可通过 https://choosealicense.com, https://kaiyuanshe.cn/license-tool 这两个网站按照步骤去选择,最终确定协议即可

-

参考

-
    -
  1. 开源许可证选择器
  2. -
  3. Choose an open source license
  4. -
  5. 博云违反 Apache 2.0 开源协议被要求整改,开源协议到底应该如何遵守?
  6. -
  7. 开源协议是什么?有哪些?如何选择?
  8. -
  9. 如何为你的代码选择一个开源协议
  10. -
  11. 开源指北 Gitee
  12. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/11/27/damn-base64/index.html b/2020/11/27/damn-base64/index.html deleted file mode 100644 index 894c195ab..000000000 --- a/2020/11/27/damn-base64/index.html +++ /dev/null @@ -1,508 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -该死的 Base64,我惹你了? | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 该死的 Base64,我惹你了? -

- - -
- - - - -

在上一个项目中,由于客观原因,双方进行数据交换,用到对媒体文件(图片)进行了 Base64 编码处理,将编码后的数据存入了数据库,使用方再从数据库中取出数据进行解码恢复成图片,在实际处理中,这是最不推荐的做法。正确有效的做法是将资源文件存入到 OSS 系统中,数据库中记录文件的地址即可。但由于项目历史原因,无法使用 OSS 来处理,虽然说技术本质不难,编码存入,解码查看而已。但由于对方没有告知具体的编码方式,询问了好几次才最终给到对应的编码方式,浪费了大量的时间去沟通和试错,得不偿失

- -

对于 Base64 ,开发者或多或少都有听过,严格意义上讲 Base64 不是加密方式,它只是一种编码方式,本篇文章就来详细的聊一聊 Base64 这个熟悉又陌生的朋友

-

什么是 Base64

-

Base64 的原理

-

常见的 Base64

-

解决实际问题

-

参考

-
    -
  1. 密码学 | 庐山真面!你认为 Base64 是加密算法吗?
  2. -
  3. 什么是Base64?
  4. -
  5. Base64编码原理分析
  6. -
  7. Base64编码
  8. -
  9. Base64算法不一致可能会导致的坑
  10. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/12/10/gradle1/index.html b/2020/12/10/gradle1/index.html deleted file mode 100644 index 487939b07..000000000 --- a/2020/12/10/gradle1/index.html +++ /dev/null @@ -1,692 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Gradle(一)基础 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Gradle(一)基础 -

- - -
- - - - -

GitHub 上 Gralde 是这样描述,“Adaptable, fast automation for all”(让一切都能快速自动化
-Gradle是一个构建工具,专注于构建自动化和对多语言开发的支持。对于在任何平台上的构建,测试,发布和部署,Gralde 提供了一种灵活的模型,可以支持从编译和打包代码到发布网站的整个生命周期。Gralde 旨在支持跨多种语言和平台的构建自动化,包括 Java,Scala,Android,Kotlin,C/C++ 和 Groovy,并于开发工具和包括 Eclipse,IntelliJ 和 Jenkins 的持续集成服务器紧密集成

- -
    -
  • Gradle official:https://gradle.org
  • -
  • Gradle docs:https://docs.gradle.org
  • -
  • Gradle plugins:https://plugins.gradle.org
  • -
-

Gradle 特点

-
    -
  1. Gradle 基于 JVM 的构建工具
  2. -
  3. 兼容支持 Maven,Ant 等
  4. -
  5. 支持基于 Groovy 的构建脚本
  6. -
  7. 编译构建执行效率更高
  8. -
  9. 支持多种语言等
  10. -
  11. 易于迁移
  12. -
-

Gradle 安装配置

-
    -
  • Gradle 官方:https://services.gradle.org/distributions/
  • -
  • Tencent 镜像:https://mirrors.cloud.tencent.com/gradle/
  • -
-
-

Tencent Gradle 镜像同步有一定的延迟,需要注意

-
-

下载

-

下载需要的版本即可,推荐最新版,这里以最新稳定版 6.7.1 为例,每个正式版本包含如下文件,我们选择 xxx-bin.zip(二进制版,只包含了二进制文件(可执行文件),没有文档和源代码) 或 xxx-all.zip(完整版,包含了各种二进制文件,源代码文件,和离线的文档)的文件即可,进行手动安装

-
1
2
3
4
5
6
7
8
9
10
gradle-6.7.1
├── gradle-6.7.1-wrapper.jar.sha256 # wrapper.jar hash 校验文件
├── gradle-6.7.1-docs.zip # gradle 文档压缩文件
├── gradle-6.7.1-docs.zip.sha256 # gradle 文档 hash 校验文件
├── gradle-6.7.1-src.zip # gradle 源码版,只包含了 Gradle 源代码,不能用来编译你的工程
├── gradle-6.7.1-src.zip.sha256 # gradle 源码版 hash 校验文件
├── gradle-6.7.1-bin.zip # gradle 核心压缩文件
├── gradle-6.7.1-bin.zip.sha256 # gradle 核心 hash 校验文件
├── gradle-6.7.1-all.zip # gradle 全部资源压缩文件
└── gradle-6.7.1-all.zip.sha256 # gradle 全部资源 hash 校验文件
-

当然如果你使用的 macOS 系统,且也已经安装了 homebrew 包管理工具,那么同样你也可以使用 brew 命令来安装 Gradle,那么你将不需要再去手动配置 Gradle 的环境,它的安装默认路径在 /usr/local/bin/gradle,安装完成后你就可以使用 gradle 的相关命令

-
1
2
3
4
5
6
# gradle 安装
brew install gradle
# gradle 升级
brew upgrade gradle
# 检查是否安装成功
gradle -v
-

配置

-

手动下载解压的文件进行安装,则需要配置 Gradle 的环境,这样方便我们在任何地方都可以调用 Gradle 的命令,对于 macOS 上手动安装配置 Gradle 环境的操作,可以参考 MacBook Pro 初始化 这篇文章 Gradle 配置

-

对于 Windows 系统,按照如下步骤进行添加环境变量,我这里 Windows 上为了和项目中 Gradle 版本有所区分,配置的是 6.7 版本
-
-
-配置完成后,老规矩我们需要验证下我们的配置是否生效,在命令行中输入 gradle -v 命令,查看有 Gradle 相关的版本信息提示,我们的配置就已成功
-

-

GRADLE_HOME

-

GRADLE_HOME 这个环境变量,它主要是我们手动配置指定 GRADLE 使用的命令环境

-

GRADLE_USER_HOME

-

GRADLE_USER_HOME 指配置 Gradle 的安装下载的路径。默认 /Users/<PC NAME>/.gradle 路径,如果你在系统环境中设置了 GRADLE_USER_HOME 的环境变量,那么下载的路径就变成了你自定义设置的路径

-

Gradle 基础

-

刚刚在上面我们配置时,使用了 gradlew 命令,那这个又是啥呢,这里简单解释下,gradlew 是 gradle wrapper 的简写,对于 Gradle 构建的项目,用于解决 Gradle 安装,部署以及统一项目的 Gradle 的构建版本等一系列问题。

-

Gradle 有两个基本的概念:project 和 task,Gradle 里面的所有东西基于这两个概念

-
    -
  • project:通常指一个项目
  • -
  • task:指构建过程中的任务
  • -
-

一次构建可以有 1 到 n 个 project,而每个 project 有 1 到 n 个 task

-

Gradle 项目

-

Android 项目工程一开始就默认使用 Gradle 来构建,在 Android 领域里使用花样也是比较多,更好体现了 Gradle 的灵活性,对于后端 Spring 系列项目,现在也是越来越多的开始使用 Gradle 来构建了,在 Spring Boot 2.3.0.M1 版本官方已开始在生产环境开始使用 Gradle 代替 Maven 进行构建,测试,发布项目。这从侧面也印证了 Gradle 对于复杂庞大的系统更加友好和高效

-

对于使用 Gradle 构建的 Android 项目也好,Java 项目也好,还是 SpringBoot 项目也罢,它们都有共同的特点。在结构上有下面的相同点

-
1
2
3
4
5
6
7
8
9
10
11
project
├── ……
├── .gradle/ # 项目使用 gradle 编译生成的临时文件存放位置
├── gradle/wrapper
│ │── gradle-wrapper.jar # gradlew 核心执行文件
│ └── gradle-wrapper.properties # gradle 运行环境配置文件
├── build.gradle # 项目依赖配置,脚本配置文件
├── gradlew # Linux or macOS 下可执行脚本
├── gradlew.bat # Windows 下可执行脚本
├── settings.gradle # 配置构建应用时应将哪些模块包含在内
└── ……
-

gradle-wrapper.properties

-
    -
  • gradle-wrapper.jar 文件是项目中执行 gradlew 相关命令的具体实现,感兴趣的可以查看其中的具体源码实现
  • -
  • gradle-wrapper.properties 是 Gradle 项目版本管理的核心 -
      -
    • distributionBase=GRADLE_USER_HOME:指定了 wrapper 保存下载的 Gradle 的主路径
    • -
    • distributionPath=wrapper/dists:指定了 wrapper 保存下载的 Gradle 的子路径
    • -
    • zipStoreBase=GRADLE_USER_HOME:指定了 wrapper 保存下载 gradle-6.7.1-bin.zip 文件的主路径
    • -
    • zipStorePath=wrapper/dists:指定了 gradle-6.7.1-bin.zip 文件的子路径
    • -
    • distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-bin.zip:gradle 文件下载的源地址
    • -
    -
  • -
-

distributionBase 和 zipStoreBase 有两种取值

-
    -
  • GRADLE_USER_HOME:默认使用方式,表示用户目录,默认路径 /Users/<PC NAME>/.gradle
  • -
  • PROJECT:表示工程的当前目录,不常用
  • -
-
-

对应 Gradle 的下载及解压目录这里还需要注意下

-

Gradle 的存放地址,比如:~/.gradle/wrapper/dists/gradle-6.7.1-bin/bwlcbys1h7rz3272sye1xwiv6 这里一个看起来无规则的文件夹,我们的 gradle 下载及解压必须放在这个文件夹内,而这个看似无规则的文件夹,实质是根据 distributionUrl 路径字符串计算 md5 值得来的

-
-

build.gradle 及 settings.gradle

-

对于build.gradlesettings.gradle 文件在 Android 应用和 SpringBoot 应用是不一样,因此关于他两介绍请移步 Gradle(二)AndroidGradle(三)SpringBoot 文章进行查看

-

Gradle 依赖

-

用于声明依赖关系的配置

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
配置名称角色是否可消费是否可分解描述
api声明API依赖项NN在这里,您可以声明依赖关系,这些依赖关系会在编译时和运行时以可传递方式导出到使用者
implementation声明实现依赖性NN在这里,您可以声明纯属内部的依赖关系,而不是要向使用方公开(在运行时仍向使用方公开)
compileOnly声明仅编译依赖项NN在这里可以声明在编译时需要的依赖项,而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项
compileOnlyApi声明仅编译API依赖项NN在这里,您可以声明模块和使用者在编译时需要的依赖项,而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项
runtimeOnly声明运行时依赖项NN在这里可以声明仅在运行时才需要的依赖关系,而在编译时则不需要
testImplementation测试依赖NN在这里声明用于编译测试的依赖项
testCompileOnly声明测试仅编译依赖项NN在这里声明仅在测试编译时需要的依赖项,而不应泄漏到运行时。这通常包括在运行时找到时会被阴影化的依赖项
testRuntimeOnly声明测试运行时依赖项NN在这里可以声明仅在测试运行时才需要的依赖项,而在测试编译时则不需要
-

核心需要掌握的是 apiimplementationcompileOnlyruntimeOnly 这4种依赖方式

-
-

对于你可能看到依赖方式,compile(api),provided(compileOnly),apk(runtimeOnly) 这些方式是比较旧的依赖方式,在 gradle plugin 3.0 开始已废弃,请使用新的依赖方式

-
-

本地依赖

-

本地依赖 module lib

-

通过这种方式依赖的弊端是每次都需要构建 module,但 module 比较多时构建非常耗时,建议控制 module 的依赖数量,避免构建耗时

-
1
2
// module 需要在项目根目录下的 settings.gradle 中通过 include 引入
implementation project(':libname')
-

本地二进制 lib 依赖

-

本地的 jar 或者 aar 需要放在 module 的 libs 文件夹下,通过这种方式依赖

-
依赖 jar
-
1
2
3
4
5
// 方式一:可以一次性依赖 libs 下的所有 jar
implementation fileTree(dir: 'libs', include: ['*.jar'])

// 方式二:可以指定依赖一个或几个 jar
implementation files('libs/xxxx1.jar', 'libs/xxxx2.jar')
-
依赖 aar
-
1
2
3
4
5
6
7
8
9
10
11
12
// 在 module 的 build.gradle 中添加目录指定
repositories {
flatDir {
dirs 'libs'
}
}

// 在 dependencies 中加入对 aar 的引入
// 方式一:可以一次性依赖 libs 下所有的 aar
implementation fileTree(dir: 'libs', include: ['*.aar'])
// 方式二:可以指定依赖某一个aar
implementation files(name: 'aar-lib-name', ext: 'aar')
-

远程二进制 lib 依赖

-
1
2
3
4
5
// 依赖明确的版本,标明 group、name 和 version
implementation group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.1.1'

// 常用的简写方式引用
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
-

Gradle 命令

-

在项目中推荐使用 gradlew 命令来进行执行,这样本质是使用项目所依赖的 Gradle 版本进行执行。当然如果你本地配置了 Gradle 的环境变量,你可以将 gradlew 命令更改成 gradle 来执行

-
    -
  • gradlew clean: 清除 build 文件夹
  • -
  • gradlew check: 执行 lint 检查
  • -
  • gradlew assemble: 编译并打包你的代码,但并不运行单元测试
  • -
  • gradlew build: 编译和测试你的代码,并生成一个包含所有类与资源的文件
  • -
  • gradlew dependencies: 查看所有依赖库 -
      -
    • gradlew dependencies -configuration runtime: 查看运行时依赖库
    • -
    -
  • -
-

注意:

-
    -
  • Windows:在项目根目录,使用的是 gradlew
  • -
  • Linux or macOS:在项目的根目录,使用的是 ./gradlew
  • -
-
-

总结

-
    -
  1. 对于 Gradle 我们不需要配置 GRADLE_USER_HOME 的环境,原因是项目中已对使用 Gradle 的版本做出了统一,我们仅需要根据自身的网络需要(如果从默认地址下载很慢,则需要配置好项目依赖镜像源)做出合适的配置。而如果你需要在任何地方使用 gradle 相关的命令,则配置 GRADLE_HOME 即可
  2. -
  3. 依赖方式,我们选择 implementation 方式,这样可屏蔽掉不同应用之间因为引用了同一 lib 而不同版本造成的麻烦问题等
  4. -
-

参考

-
    -
  • gradle-wrapper.properties中各属性的含义
  • -
  • Dependency management in Gradle
  • -
  • The Java Library Plugin
  • -
  • The Distribution Plugin
  • -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/12/10/play-maze/index.html b/2020/12/10/play-maze/index.html deleted file mode 100644 index 8deb79e19..000000000 --- a/2020/12/10/play-maze/index.html +++ /dev/null @@ -1,607 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -迷宫如意琳琅图籍 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 迷宫如意琳琅图籍 -

- - -
- - - - -

- -

故宫博物院出品,奥秘之家设计制作(曾推出线下实景地铁逃脱游戏,2018联合《唐人街探案 2》推出《侦探笔记》的互动解密游戏,以及配合电影开发Crimaster),到手快一年了还没有完全解锁线上的关卡,倒不是玩不下去,而是懒,刷 B 站多香,动啥脑子,哈哈哈。言归正传,本篇记录自己解锁线上关卡的步骤,持续更新

-

进度

-
    -
  1. 初 ------------------ 100% -
      -
    • ✅ 梦入紫禁
    • -
    • ✅ 太和异象
    • -
    • ✅ 十八棵槐
    • -
    -
  2. -
  3. 壹 ------------------ 23% -
      -
    • 万寿盛筵
    • -
    • 殿前观礼
    • -
    • 礼乐度量
    • -
    • 一等画师
    • -
    • 腰牌买卖
    • -
    • 慈宁画样
    • -
    -
  4. -
  5. 贰 ------------------ 0% -
      -
    • 宫女禾心
    • -
    • 嘉祉初遇
    • -
    • 淑芳听戏
    • -
    • 戏里玄机
    • -
    • 上元之约
    • -
    -
  6. -
  7. 叁 ------------------ 0% -
      -
    • 结伴寻宝
    • -
    • 皇十五子
    • -
    • 档房探秘
    • -
    • 一路狂奔
    • -
    • 逢凶化吉
    • -
    • 五行八卦
    • -
    • 夜探御园
    • -
    -
  8. -
  9. 肆 ------------------ 0% -
      -
    • 琳琅宝藏
    • -
    • 花叶之谜
    • -
    • 宫中怪人
    • -
    • 图籍作者
    • -
    • 祸不单行
    • -
    • 五蕴皆空
    • -
    -
  10. -
  11. 伍 ------------------ 0% -
      -
    • 将破未破
    • -
    • 孤注一掷
    • -
    -
  12. -
  13. 隐 ------------------ 0% -
      -
    • 多年以后
    • -
    -
  14. -
  15. 众 ------------------ 0% -
      -
    • 众筹专属
    • -
    -
  16. -
-

-

梦入紫禁

-

太和异象

-

十八棵槐

-

-

万寿盛筵

-

殿前观礼

-

礼乐度量

-

一等画师

-

腰牌买卖

-

慈宁画样

-

-

宫女禾心

-

嘉祉初遇

-

淑芳听戏

-

戏里玄机

-

上元之约

-

-

结伴寻宝

-

皇十五子

-

档房探秘

-

一路狂奔

-

逢凶化吉

-

五行八卦

-

夜探御园

-

-

琳琅宝藏

-

花叶之谜

-

宫中怪人

-

图籍作者

-

祸不单行

-

五蕴皆空

-

-

将破未破

-

孤注一掷

-

-

多年以后

-

-

众筹专属

-

附录

-
    -
  1. 奥秘之家官网:http://www.itaotuo.com/
  2. -
  3. 《唐人街探案 2》之《侦探笔记》:https://www.zhihu.com/question/267341464
  4. -
  5. 《唐人街探案 3》之《侦探笔记》:https://zhongchou.modian.com/item/90315.html
  6. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/12/10/play-ssd/index.html b/2020/12/10/play-ssd/index.html deleted file mode 100644 index 4080e5856..000000000 --- a/2020/12/10/play-ssd/index.html +++ /dev/null @@ -1,506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -搞定 m.2 接口 SSD | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 搞定 m.2 接口 SSD -

- - -
- - - - -

- -

公司原装配置电脑磁盘性能太差,实在是不能满足我的日常骚操作,然后就自己买了一个 m.2 接口的 SSD 硬盘,毕竟电脑之前已经有系统了,而且也已经安装好了开发环境,如果现在在新的 SSD 上直接安装新的系统,那么需要将之前的开发环境再折腾一遍,实在是伤不起。那么有没有别的方式。你别说哦,还真的有,方法是用一些工具对现有系统进行 clone 到新的 SSD 磁盘上。这都很好办,比如:傲梅分区助手DiskGenius 都有系统迁移功能,可参考文章下方的参考地址,内有视频教程

-
-

注意:要设置好设置默认系统启动引导为新的磁盘

-
-

问题

-

一开始,我觉得这么简单的操作能有什么问题,迁移完系统,并设置好系统引导,然而我发现并不能按照预期使用 SSD 来启动,试了好几遍,调整了 BIOS 的启动选项,依旧不能解决。后来我将原系统的磁盘拆下来,只留 SSD 磁盘,开机就能按照预期启动了,正常后在把原系统磁盘再装回去,同时记得检查下系统引导,确保还依旧是使用 SSD 系统盘

-

参考

-
    -
  1. SSD系统迁移工具:轻松迁移系统到SSD
  2. -
  3. 使用分区助手快速将Windows系统迁移到新磁盘
  4. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/12/15/gradle2/index.html b/2020/12/15/gradle2/index.html deleted file mode 100644 index a3ec1dc3e..000000000 --- a/2020/12/15/gradle2/index.html +++ /dev/null @@ -1,561 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Gradle(二)Android | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Gradle(二)Android -

- - -
- - - - -

在上一篇 Gradle 的文章中,已经对 Gradle 有了一定的认识,Gradle 在 Android 有着广泛的应用,用作 Android 包依赖管理,应用构建,测试,等一些列自动化,我们本篇就来了解下在 Android 领域 Gradle 的使用。其实 Android 项目结构和之前在第一篇 Gradle 项目结构基本相同,只是在 module 级别多了的 proguard-rules.pro。对于不管是 Android 项目或是 Spring 系列项目的子 module 都会有 build.gradle 文件

- -

Project 级别

-

build.gradle

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// gradle 脚本执行所需依赖,分别是对应的maven库和插件
buildscript {
repositories {
google()
jcenter()
}
// 声明依赖 Android Gradle 插件版本
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

// 项目所有 module 配置需要的依赖
allprojects {
repositories {
google()
jcenter()
}
}

// 一个 clean 任务,用于删除 build 目录的文件
task clean(type: Delete) {
delete rootProject.buildDir
}
-

settings.gradle

-
1
2
// 默认指的是创建 Android 项目生成的 app 模块,也是默认的应用启动模块
include ':app'
-

Module 级别

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// 表示这是一个应用程序模块,可直接运行
apply plugin: 'com.android.application'

// 编译时间
static def releaseTime() {
return new Date().format('yyyy-MM-dd', TimeZone.getTimeZone('UTC'))
}

android {
// 编译 Android 版本
compileSdkVersion 29
// 默认配置
defaultConfig {
// 应用 ID,手机中用于识别应用的唯一标识
applicationId "org.incoder.android"
// 目标 Android 版本
targetSdkVersion 29
// 申明应用可超过 65536 的方法,可参考:https://developer.android.google.cn/studio/build/multidex?hl=zh_cn
multiDexEnabled true
// 申明要使用AndroidJUnitRunner进行单元测试
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

// 签名配置,相关信息放置在 gradle.properties 文件中
signingConfigs {
debug {
storeFile file(DEBUG_STORE_FILE)
storePassword DEBUG_STORE_PASSWORD
keyAlias DEBUG_KEY_ALIAS
keyPassword DEBUG_KEY_PASSWORD
}
release {
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
v2SigningEnabled true
}
}

buildTypes {
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
// 签名
// signingConfig signingConfigs.debug
manifestPlaceholders = [
//JPush
JPUSH_APPKEY : "",
JPUSH_CHANNEL: "",
// Pgy
PGYER_APPID : "7907554687e4c116316feedb3820ce52",
// Bugly
BUGLY_APPID : "",
VERSION_NAME : "0.1.0",
]
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}

release {
// 混淆
minifyEnabled false
// Zipalign优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources false
// 前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// 签名
signingConfig signingConfigs.release
// AppAnalytics key
manifestPlaceholders = [
// JPush
JPUSH_APPKEY : "",
JPUSH_CHANNEL: "",
// Pgy
PGYER_APPID : "7907554687e4c116316feedb3820ce52",
// Bugly
BUGLY_APPID : "",
VERSION_NAME : "0.1.0",
]
}
}

// 重命名安装包
android.applicationVariants.all {
variant ->
variant.outputs.all {
output ->
output.outputFileName = variant.flavorName + buildType.name +
"_" + releaseTime() + ".apk"
}
}

// 产品变种
flavorDimensions "minSDK"

// 针对不同渠道的配置
productFlavors {
// 测试环境渠道包
dev {
applicationId 'org.incoder.test'
minSdkVersion 19
// 测试环境IP配置 API 接口地址
buildConfigField 'String', 'API', '"http://xxx.xxx.xxx.xxx:8888"'
versionCode 2020122501
versionName "2.0"
// 指定产品变种
dimension "minSDK"
}
// 正式环境渠道包
rel {
applicationId "org.incoder.android"
minSdkVersion 16
// 正式环境域名 API 接口地址
buildConfigField 'String', 'API', '"http://api.xxx.xxx/"'
versionCode 2020122501
versionName "2.1"
// 指定产品变种
dimension "minSDK"
}
}

// 过滤指定产品变种(渠道,构建类型)
// variantFilter { variant ->
// def names = variant.flavors*.name
// def isDebug = variant.buildType.debuggable
// // To check for a certain build type, use variant.buildType.name == "<buildType>"
// if (names.contains("rel") && isDebug) {
// // Gradle ignores any variants that satisfy the conditions above.
// setIgnore(true)
// }
// }

// 多渠道配置
productFlavors.all {
flavor ->
flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
}

// 执行lint检查,有任何的错误或者警告提示,都会终止构建
lintOptions {
disable 'MissingTranslation', 'ExtraTranslation'
// abortOnError一定要设为false,这样即使有报错也不会停止打包了
abortOnError false
// 在打包Release版本的时候进行检测,可以打开,这样报错还会显示出来
checkReleaseBuilds false
}

dexOptions {
jumboMode true
javaMaxHeapSize "4g"
}

// 打包时的配置
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/rxjava.properties'
}

aaptOptions.cruncherEnabled = false
aaptOptions.useNewCruncher = false

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

// 项目依赖的包
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:support-v13:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:support-vector-drawable:28.0.0'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
testImplementation 'junit:junit:4.12'
}
-

关于 module 中的 build.gradle 配置文件中的各项已在示例中加入了注释说明,其中一些配置,这里再简单的说明下

-

apply plugin

-

这里的 apply plugin 有两种模式:

-
    -
  1. com.android.application:表示这是一个应用程序模块
  2. -
  3. com.android.library:表示这是一个库模块
  4. -
-

前者可以直接运行,后着是依附别的应用程序运行

-

buildTypes

-

这里主要是生成安装文件的配置信息,一个 debug 类型,用于指定生成测试版安装文件配置,可忽略不写;另一个是 release,用于指定生成正式版安装文件的配置。

-
    -
  • minifyEnabled:是否对代码进行混淆,默认 false
  • -
  • proguardFiles:指定混淆的规则文件,默认指定了 proguard-android.txt 文件和 proguard-rules.pro 文件。 -
      -
    • proguard-android.txt:默认的混淆文件,里面定义了一些通用的混淆规则
    • -
    • proguard-rules.pro:位于当前项目的根目录下,可以在该文件中定义一些项目特有的混淆规则
    • -
    -
  • -
  • buildConfigField:可用于解决不同渠道不同的服务地址,或不同渠道 LOG 打印控制等
  • -
  • debuggable:是否支持断点调试,release 默认为 false,debug 默认 true
  • -
  • jniDebuggable:是否可以调试 NDK 代码,使用 lldb 进行 C 和 C++ 代码调试,release 默认为 false
  • -
  • signingConfig:设置签名信息,通过 singingConfig.release 或 singingConfig.debug,配置相应的签名,但是添加此配置前需要先添加 singingConfig 闭包
  • -
  • renderscriptDebuggable:是否开启渲染脚本,就是一些 C 写的渲染方法,默认为 false
  • -
  • renderscriptOptimLevel:渲染等级,默认为 3
  • -
  • zipAlignEnabled:是否对 apk 包执行 zip 对齐优化,减少 zip 体积,提高运行效率,release 和 debug 都默认 true
  • -
  • pseudoLocalesEnabled:是否在 apk 中生成伪语言环境,帮助国际化,一般很少使用
  • -
  • applicationIdSuffix:和 defaultConfig 中配置一样,指在 applicationId 中添加一个后缀
  • -
  • versionNameSuffix:添加版本名称的后缀,一般使用较少
  • -
-

productFlavors

-

这个配置主要是解决应用发布在不同应用市场,而需要对不同应用市场做一些不同配置,比如包名,应用名,以及一些统计,而需要不同渠道统计 ID 等

-

packagingOptions

-

packagingOptions 常见的设置项有 exclude、pickFirst、doNotStrip、merge

-
    -
  1. exclude:过滤掉某些文件或者目录不添加到APK中,作用于APK,不能过滤aar和jar中的内容
    1
    2
    3
    4
    packagingOptions {
    exclude 'META-INF/**'
    exclude 'lib/arm64-v8a/libmediaplayer.so'
    }
    -
  2. -
  3. pickFirst:匹配到多个相同文件,只提取第一个。只作用于APK,不能过滤aar和jar中的文件
    1
    2
    3
    4
    packagingOptions {
    pickFirst "lib/armeabi-v7a/libaaa.so"
    pickFirst "lib/armeabi-v7a/libbbb.so"
    }
    -
  4. -
  5. doNotStrip:可以设置某些动态库不被优化压缩
    1
    2
    3
    4
    packagingOptions{
    doNotStrip "*/armeabi/*.so"
    doNotStrip "*/armeabi-v7a/*.so"
    }
    -
  6. -
  7. merge:将匹配的文件都添加到APK中,和pickFirst有些相反,会合并所有文件
    1
    2
    3
    4
    packagingOptions {
    merge '**/LICENSE.txt'
    merge '**/NOTICE.txt'
    }
    -
  8. -
-

统一版本

-

应用由多个 module 构成,而不同地方引用的包,需要做到全局的统一时,可以创建一个 xxx.gradle 的文件(这里的 xxx,自行取一个表明含义的内容即可),然后在使用的地方时,统一调用定义的版本即可,使用步骤如下

-
    -
  1. 创建 xxx.gradle 文件(一般放在项目的根目录,和顶级 build.gradle 文件在同一层级),并添加如下内容,可根据自身需要调整
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    ext {

    android = [
    compileSdkVersion: 29,
    buildToolsVersion: "29.0.2",
    minSdkVersion : 19,
    targetSdkVersion : 29,
    versionCode : 2020010102,
    versionName : "0.1.0"
    ]

    version = [
    androidSupportSdkVersion: "29.0.0",
    retrofitSdkVersion : "2.6.3",
    okhttpSdkVersion : "4.3.0",
    dagger2SdkVersion : "2.22.1",
    glideSdkVersion : "4.9.0",
    butterknifeSdkVersion : "10.2.1",
    rxlifecycle2SdkVersion : "2.2.1",
    espressoSdkVersion : "3.0.2",
    canarySdkVersion : "1.5.4"
    ]

    // Android support 与 AndroidX support 对比
    // https://developer.android.google.cn/jetpack/androidx/migrate

    // support 库说明
    // https://developer.android.com/topic/libraries/support-library/features?hl=zh-cn
    dependencies = [
    // support
    "appcompat" : "androidx.appcompat:appcompat:1.1.0",
    "annotations" : "androidx.annotation:annotation:1.0.0",
    "cardview-v7" : "androidx.cardview:cardview:1.0.0",
    "constraint-layout" : "androidx.constraintlayout:constraintlayout:1.1.3",
    "swiperefreshlayout" : "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0",
    "material" : "com.google.android.material:material:1.1.0",
    "viewpager" : "androidx.viewpager:viewpager:1.0.0",
    "recyclerview" : "androidx.recyclerview:recyclerview:1.1.0",
    "vectordrawable" : "androidx.vectordrawable:vectordrawable:1.1.0",
    "support-v4" : "androidx.legacy:legacy-support-v4:1.0.0",

    // network
    "retrofit" : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}",
    "retrofit-converter-gson" : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}",
    "retrofit-converter-simplexml": "com.squareup.retrofit2:converter-simplexml:${version["retrofitSdkVersion"]}",
    "retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}",
    "okhttp3" : "com.squareup.okhttp3:okhttp:${version["okhttpSdkVersion"]}",
    "okhttp3-logging-interceptor" : "com.squareup.okhttp3:logging-interceptor:${version["okhttpSdkVersion"]}",
    "mockwebserver" : "com.squareup.okhttp3:mockwebserver:${version["okhttpSdkVersion"]}",
    "glide" : "com.github.bumptech.glide:glide:${version["glideSdkVersion"]}",
    // (annotationProcessor)
    "glide-compiler" : "com.github.bumptech.glide:compiler:${version["glideSdkVersion"]}",
    "glide-loader-okhttp3" : "com.github.bumptech.glide:okhttp3-integration:${version["glideSdkVersion"]}",

    // view
    "butterknife" : "com.jakewharton:butterknife:${version["butterknifeSdkVersion"]}",
    "butterknife-compiler" : "com.jakewharton:butterknife-compiler:${version["butterknifeSdkVersion"]}",
    "brvah" : "com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.49-androidx",
    "psid" : "com.oushangfeng:PinnedSectionItemDecoration:1.3.2-androidx",
    "material-dialogs" : "com.afollestad.material-dialogs:core:2.8.1",
    "material-input" : "com.afollestad.material-dialogs:input:2.8.1",
    "material-files" : "com.afollestad.material-dialogs:files:2.8.1",
    "material-color" : "com.afollestad.material-dialogs:color:2.8.1",
    "material-datetime" : "com.afollestad.material-dialogs:datetime:2.8.1",
    "pickerview" : "com.contrarywind:Android-PickerView:4.1.8",
    "photoview" : "com.github.chrisbanes.photoview:library:2.0.0",
    "lottie" : "com.airbnb.android:lottie:3.0.1",
    "badge-view" : "q.rorbin:badgeview:1.1.3",

    // rx2
    "rxandroid2" : "io.reactivex.rxjava2:rxandroid:2.1.1",
    "rxjava2" : "io.reactivex.rxjava2:rxjava:2.2.16",
    // https://github.com/VictorAlbertos/RxCache
    "rxcache2" : "com.github.VictorAlbertos.RxCache:runtime:1.8.3-2.x",
    // https://github.com/tbruyelle/RxPermissions
    "rxpermissions2" : "com.github.tbruyelle:rxpermissions:0.10.2",

    // tools(implementation)
    "dagger2" : "com.google.dagger:dagger:${version["dagger2SdkVersion"]}",
    "dagger2-android" : "com.google.dagger:dagger-android:${version["dagger2SdkVersion"]}",
    "dagger2-android-support" : "com.google.dagger:dagger-android-support:${version["dagger2SdkVersion"]}",
    "eventbus" : "org.greenrobot:eventbus:3.1.1",
    "gson" : "com.google.code.gson:gson:2.8.5",
    // https://projectlombok.org/setup/android
    "lombok" : "org.projectlombok:lombok:1.18.8",
    "multidex" : "com.android.support:multidex:1.0.3",
    "arouter-api" : "com.alibaba:arouter-api:1.4.1",
    "arouter-compiler" : "com.alibaba:arouter-compiler:1.2.2",
    //(annotationProcessor)
    "dagger2-compiler" : "com.google.dagger:dagger-compiler:${version["dagger2SdkVersion"]}",
    "dagger2-android-processor" : "com.google.dagger:dagger-android-processor:${version["dagger2SdkVersion"]}",

    // test
    "junit" : "junit:junit:4.12",
    "androidJUnitRunner" : "androidx.test.runner.AndroidJUnitRunner",
    "runner" : "androidx.test:runner:1.1.1",
    "espresso-core" : "androidx.test.espresso:espresso-core:3.2.0",
    "espresso-contrib" : "androidx.test.espresso:espresso-contrib:3.2.0",
    "espresso-intents" : "androidx.test.espresso:espresso-intents:3.3.0",
    "canary-debug" : "com.squareup.leakcanary:leakcanary-android:${version["canarySdkVersion"]}",
    "canary-release" : "com.squareup.leakcanary:leakcanary-android-no-op:${version["canarySdkVersion"]}",
    "umeng-analytics" : "com.umeng.analytics:analytics:6.0.1",

    // util
    // https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/README-CN.md
    "utilcode" : "com.blankj:utilcode:1.23.7",

    // help
    "logger" : "com.orhanobut:logger:2.2.0",
    // https://www.pgyer.com/doc/view/new_sdk_android_guide
    "pgy" : "com.pgyersdk:sdk:3.0.3",
    // SDK 包
    // https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613
    // https://jcenter.bintray.com/com/tencent/bugly/crashreport/
    "crashreport" : "com.tencent.bugly:crashreport:3.1.0",
    // 升级 SDK 包
    // https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613
    // https://jcenter.bintray.com/com/tencent/bugly/crashreport_upgrade/
    "bugly-crash-upgrade" : "com.tencent.bugly:crashreport_upgrade:1.4.2",
    // NDK 动态库
    // https://bugly.qq.com/docs/release-notes/release-android-ndk/?v=20180709165613
    // https://jcenter.bintray.com/com/tencent/bugly/nativecrashreport/
    "bugly-ndk" : "com.tencent.bugly:nativecrashreport:3.7.1",

    ]
    }
    -
  2. -
  3. 在顶级的 build.gradle 文件底部,表明添加对 xxx.gradle 的使用
    1
    apply from: "xxx.gradle"
    -
  4. -
  5. 在 module 级别的 build.gradle 文件中,修改哪些固定写死的依赖版本
    1
    2
    3
    4
    // 之前固定的版本
    minSdkVersion 19
    // 修改通过 xxx.gradle 中定义的版本
    minSdkVersion rootProject.ext.android["minSdkVersion"]
    -
  6. -
-

参考

-
    -
  1. Android Gradle 插件版本说明
  2. -
  3. 配置构建
  4. -
  5. 配置构建变体
  6. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/12/16/gradle3/index.html b/2020/12/16/gradle3/index.html deleted file mode 100644 index ca3b2ec98..000000000 --- a/2020/12/16/gradle3/index.html +++ /dev/null @@ -1,559 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Gradle(三)SpringBoot 单工程 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Gradle(三)SpringBoot 单工程 -

- - -
- - - - -

Gradle(一)基础 的文章中,我们已经对 Gradle 有了一定的认识,本篇来看一看在后端开发中使用 Gradle 构建 SpringBoot 项目的开发。通常有两种方式来构建项目,第一种:每个功能模块即是一个代码工程,用一个 Git 仓库来管理,每个模块只负责完成一件事情;第二种:整个系统的多个模块聚合在一个代码工程里面,也就是我们常说的多模块项目,本篇先来讲一讲单工程

- -

工程选择

-

对于单工程,和聚合工程的选择主要根据你所在项目团队的大小,项目分工,以及项目的复杂程度等来考虑。

-

单工程:适用于项目分工明确,项目庞大复杂,架构服务边界划分明确,配套的自动化等设施完善
-聚合工程:适用于项目人员不是很多,项目功能一般,需要一个人集中化管理等

-

环境

-
    -
  • OS:macOS 11.1
  • -
  • JDK:JDK1.8
  • -
  • Gradle:6.7.1-bin
  • -
  • IDE:IntelliJ IDEA Community 2020.3
  • -
  • SpringBoot:2.4.1
  • -
-

build.gradle

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 项目使用插件,可从 https://plugins.gradle.org 库中寻找合适的插件
plugins {
id 'org.springframework.boot' version '2.4.1'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}

// 这里和 maven类似,用于项目唯一坐标
group = 'com.example'
version = '0.0.1-SNAPSHOT'
// 项目兼容版本
sourceCompatibility = '1.8'

// 依赖第三方jar从哪个仓库去下载
repositories {
mavenCentral()
}

// 项目所需的第三方依赖
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

// 测试相关
test {
useJUnitPlatform()
}
-

一个 SpringBoot 项目基本的 build.gradle 文件由 plugins,项目坐标,repositories,dependencies,test 基础内容组成。关于 plugins 使用常见有两种方式,核心的依赖,是没有版本号,它和你使用的 Gradle 关联,你无需过多关系这些核心插件的依赖版本

-
1
2
3
4
5
6
7
// 旧方式
apply plugin: 'java'

// 新方式(推荐)
plubins {
id 'java'
}
-

settings.gradle

-

用于项目模块管理,由于这个单工程,这里只有一个模块

-
1
rootProject.name = 'demo'
-

多环境

-

可通过自定义 task 来出来

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// prod
tasks.register("bootRunProd") {
group = "application"
description = "Runs the Spring Boot application with the prod profile"
doFirst {
tasks.bootRun.configure {
systemProperty("spring.profiles.active", "prod")
}
}
finalizedBy("bootRun")
}

// dev
tasks.register("bootRunDev") {
group = "application"
description = "Runs the Spring Boot application with the dev profile"
doFirst {
tasks.bootRun.configure {
systemProperty("spring.profiles.active", "dev")
}
}
finalizedBy("bootRun")
}
-

启动方式

-
    -
  • -

    方式一:图形化界面中,直接运行对应环境
    -

    -
  • -
  • -

    方式二:在命令行中,使用命令来运行对应环境,比如 gradlew bootRunDev
    -

    -
  • -
  • -

    方式三:当然你也可以在启动时指定你需要激活的环境

    -
    1
    2
    # 这里激活的 test 环境,把 ${jar_name} 参数换成对应启动的应用文件
    java -jar ${jar_name} --spring.profiles.active=test
    -
  • -
-

排除依赖

-
1
2
3
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
-

打包

-

打包时需要,注意我们的 SpringBoot 应用它本质上是一个 bootJar(Fatjar) 应用,因此需要将应用打成一个 bootJar(Fatjar)。而对于什么是 bootJar 和 jar 的区别,可以查看之前在 SpringBoot(二) 启动分析JarLauncher 文章中对于 jar 规范 说明

-

打包方式

-
    -
  • -

    方式一:图形化操作

    -
  • -
  • -

    方式二:命名执行

    -
    1
    2
    3
    # 在项目的根目录执行,Windows 使用:gradlew;Linux/macOS:./gradlew
    # 当然如果那你已安装且配置好 gradle 的环境,你可以直接使用 gradle 代替 ./gradlew 的相关命令
    gradlew bootJar
    -
  • -
-

发布

-

参考

-
    -
  1. SpringBoot+gradle 构建多模块项目
  2. -
  3. IDEA 2020.2 + Gradle 6.6.1 + Spring Boot 2.3.4 创建多模块项目
  4. -
  5. Spring-boot 2.3.x 源码基于Gradle编译
  6. -
  7. 用 Gradle 构建 Spring Boot 项目
  8. -
  9. 使用 Gradle 构建 springboot 多模块项目,并混合groovy开发
  10. -
  11. Spring Boot Gradle Plugin Reference Guide
  12. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/12/31/milepost1/index.html b/2020/12/31/milepost1/index.html deleted file mode 100644 index 2453117f7..000000000 --- a/2020/12/31/milepost1/index.html +++ /dev/null @@ -1,501 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -第 100 篇原创文章 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 第 100 篇原创文章 -

- - -
- - - - -

未曾想过,居然能写到第 100 篇文章。虽然大部分文章都是线性流水操作,但全部是自己经过实践的总结;虽然没有精彩的故事,但都是自己成长的思考;虽然有时一篇文章需要要长达一个多月的反复核对,但还是能默默坚持。只是这第 100 篇来的有点晚,断断续续大概有 3 年的时间,时间是个坏老头,把我给你写情话,悄悄的改成了谎话!

- -

回想起之前写文章主要是为了记录一些操作步骤和一些知识点,方便遇到类似问题,快速定位,解决问题。但随着文章的越写越多,包含的内容也越来越多,需要去了解的知识也越来越多,真的是有一种 “你知道的越多,你不知道的越多” 的感觉,这种感觉让我对待每个知识点都能有往深去深挖的动力,对每一个知识点用自己文字将它讲出来时有一种让我欲罢不能成就感,这或许就是上瘾吧

-

总结下第一个里程碑,主要是平时接触到领域算是一些入门级别的一些文章,以及一些比较粗浅的见闻,缺乏深层次的剖析和思考,这也是第二个里程碑首要做的事情,把每个接触到的知识点进行深挖,打通自己的技术栈。技术领域能不能走得远,很大程度上并不是你的技能宽度,而是深度,是在一个方向上的深耕,并且对于底层的技能也是要有足够的涉猎,只有这样就算是技术的花样任它怎么去变,你都能以不变应万变(透过现象找到本质);第二点是是对第一阶段内容完善补充;第三点就是打磨自己的语言表达能力,让文章更加的通俗易懂;第四点就是不能太拖拉,要保持高效的内容输出

-

这一阶段,对开源项目贡献评价最高的是 rap2-delos 项目了。努力在接下来的里程中,提高质量和参与度,争取早日在大型项目中做到 Committer

-

很感谢这一路走来,大伙对我的认可和期待以及赞赏,下一个里程碑我们见~

-

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2020/12/31/springboot11/index.html b/2020/12/31/springboot11/index.html deleted file mode 100644 index f73b81376..000000000 --- a/2020/12/31/springboot11/index.html +++ /dev/null @@ -1,509 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SpringBoot 源码构建 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- SpringBoot 源码构建 -

- - -
- - - - -

前两天刚刚学习了 Gradle 构建 SpringBoot 项目,再查看官方文档时,得知 SpringBoot 从 Spring Boot 2.3.0.M1 版本开始完全切换到使用 Gradle 来构建项目,那么本篇文章就来实践,基于源码来编译构建 SpringBoot,话不多说,本次构建构建是 2020 年的最后一次发布的版本 2.4.1

- -

环境

-
    -
  • OS:macOS 11.1
  • -
  • JDK:JDK1.8
  • -
  • Gradle:6.7.1-bin
  • -
  • IDE:IntelliJ IDEA Community 2020.3
  • -
-

Gradle 版本通过 https://github.com/spring-projects/spring-boot/blob/master/gradle/wrapper/gradle-wrapper.properties 文件可知,使用的 6.7.1-bin,那么本地也使用该版本编译,对于 Gradle 的安装可参考 Gradle(一)基础 文章

-

获取源码

-
1
2
# 这里使用 cnpmjs 来提高 clone 速度
git clone https://github.com.cnpmjs.org/spring-projects/spring-boot.git
-

编译构建

-

使用 IDEA 打开项目,会自动创建索引以及,下载项目的依赖,由于依赖的 jar 比较多,建议使用 阿里云 镜像,关于 Gradle 怎么修改依赖镜像源,可参考 专治各种网络不服 文章,阿里云镜像能加速大部分的 jar,但有一部分在阿里云上并没有,你可以通过手动方式导入到本地

- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2021/02/12/wechat-mini1/index.html b/2021/02/12/wechat-mini1/index.html deleted file mode 100644 index 8a928afd1..000000000 --- a/2021/02/12/wechat-mini1/index.html +++ /dev/null @@ -1,568 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -微信小程序之 Vant实战(一) | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 微信小程序之 Vant实战(一) -

- - -
- - - - -

过年正好时间比较集中,可以把之前的一个想法付诸实践,之前一直想给老爸做一个类似于账单管理的应用,方便他每天把客户需要物品记录成一个清单进行管理,其中主要包含已下功能点。其一,支持添加任务列表(账单);其二,支持任务列表分享(账单)。以上是我的第一期规划功能规划,话不多说我们就一起来跟着我来完成这个小程序的开发吧,本篇主要讲小程序的初始化相关工作

- -

这是我第一次来开发小程序,虽然之前有开发 Android 客户端的经验,有一定的客户端经验,但是小程序却一直没有去实践,我主要是觉得小程序的使用体验真的很差。但随着现在人们的硬件设备越来越好,并且微信团队在应用底层也做了很多的扩展和优化,现在使用小程序开发轻量级的应用还是很方便且高效

-

环境及选型

-
    -
  1. OS:macOS(11.2.1)
  2. -
  3. IDE:WeChat Devtools(1.05.2102010)
  4. -
  5. Node:v15.5.0
  6. -
  7. Vant:1.6.7
  8. -
  9. 微信小程序账号
  10. -
-
-

关于账号的申请,这里不做讲解,请自行解决

-
-

初始化项目

-

创建项目

-

不废话,直接看图

-

-

项目结构

-

项目是一个基于云开发的方式,创建完成后会包含云相关的一些操作实例

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bill
├── cloudfunctions/ # 云函数管理【清空当前文件夹下的内容】
│ │── callback/
│ │── echo/
│ │── login/
│ └── openapi/
├── minprogram/
│ │── components/ # 组件【清空当前文件夹下内容】
│ │── images/ # 图片管理【清空当前文件夹下内容】
│ │── pages/ # 页面管理【除 index 页面,其余都删除】
│ │ │── addFunction/
│ │ │── chooseLib/
│ │ │── databaseGuide/
│ │ │── deployFunctions/
│ │ │── im/
│ │ │── index/
│ │ │ │── index.js
│ │ │ │── index.json
│ │ │ │── index.wxml
│ │ │ │── index.wxss
│ │ │ └── user-unlogin.png
│ │ │── openapi/
│ │ │── storageConsole/
│ │ └── userConsole/
│ │── style/ # 样式管理
│ │── app.js # 项目入口逻辑管理
│ │── app.json # 组件库配置
│ │── app.wxss # 全局样式设置
│ └── sitemap.json #
├── project.config.json # 项目配置文件
└── README.md # 项目说明
-

精简项目

-

从上面我们可知初始化的项目,包含了一些示例,我们对其精简

-
    -
  1. 清空 cloudfunctions 目录下的云函数内容
  2. -
  3. 根据项目结构里的备注,进行删除相关的文件 -
      -
    • 清空 components 文件夹下的组件
    • -
    • 清空 images 文件夹中的内容
    • -
    • 删除 pages 文件夹下, index 的文件夹
    • -
    • 删除 index 文件夹下的 user-unlogin.png 文件
    • -
    -
  4. -
  5. 修改文件内容 -
      -
    • 清空 index 文件夹下 index.wxml,index.wxss 文件中的内容
    • -
    • 修改 index 文件夹下 index.js 文件内容
    • -
    • 清空 minprogram 文件夹下 app.wxss 文件中的样式内容
    • -
    -
  6. -
  7. 修改配置 -
      -
    • 移除 app.json 文件中 已经移除掉的 pages 的配置
    • -
    • 修改 project.config.json 文件,移除 miniprogram 的配置
    • -
    -
  8. -
-

添加 vant 组件

-

整个步骤,如下截图
-

-
-

执行命令根据官方提供的方式和自身喜好选择,我这里使用的是 yarn 命令进行安装相关的依赖

-
-

测验效果

-

这里以添加 Button 为例来查看是否生效

-
    -
  1. 在 pages/index 路径下的 index.json 文件中,添加 vant 的 Button 组件
    1
    2
    3
    4
    5
    {
    "usingComponents": {
    "van-button": "@vant/weapp/button/index"
    }
    }
    -
  2. -
  3. 在 pages/index 路径下的 index.wxml 文件中,添加 vant 的相关组件
    1
    2
    3
    4
    <van-button type="primary">主要按钮</van-button>
    <van-button type="info">信息按钮</van-button>
    <van-button type="warning">警告按钮</van-button>
    <van-button type="danger">危险按钮</van-button>
    -
  4. -
  5. 编译,在模拟器中查看效果
  6. -
-
-

对于引用的组件,是公共的,可以写在 app.json 文件中

-
-

参考

-
    -
  1. 官方开发文档
  2. -
  3. 微信小程序组件库Vant weapp的使用与weui零基础入门课程
  4. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2021/03/06/scenically/index.html b/2021/03/06/scenically/index.html deleted file mode 100644 index 471ffa021..000000000 --- a/2021/03/06/scenically/index.html +++ /dev/null @@ -1,587 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -琅嬛福地 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 琅嬛福地 -

- - -
- - - - -

在金庸武侠《天龙八部》中,“琅嬛福地”存放了无崖子和李秋水搜罗天下各门各派的武功,江湖人士练成这里的一门武功绝学,就能在江湖中有自己的一席之地。而这里存放了我计算机相关学习、实践应用,以及经常使用的一些网站资源

- -

计算机网络

- -

数据结构与算法

- -

操作系统

- -

计算机组成原理

- -

编译原理

- -

资源

- -

参考

-
    -
  1. 聊一聊我在B站上自学编程的经历吧
  2. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2021/03/16/minio/index.html b/2021/03/16/minio/index.html deleted file mode 100644 index 7e3ea056d..000000000 --- a/2021/03/16/minio/index.html +++ /dev/null @@ -1,562 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -OSS 之 Minio 初体验 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- OSS 之 Minio 初体验 -

- - -
- - - - -

MinIO 是一个基于 Apache License v2.0 开源协议使用 Go 语言开发的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

- -

MinIO 包含 MinIO Server, MinIO Client 以及方便开发基于不同编程语言使用的 MinIO SDK,这三部分组成,使用步骤也很简单,在服务器上安装 MinIO Server 应用,在项目中集成对应的 MinIO SDK,然后按照你的业务情况编写相应的实现即可,在开始前,我们先看看为什么我选择 MiniIO 作为自建的 OSS 服务

-
    -
  1. MinIO 由良好的存储机制
  2. -
  3. 兼容 Amason 的 S3 分布式存储
  4. -
  5. 天然的支持云原生
  6. -
  7. 支持私有部署,可分布式,可单机,100%开源
  8. -
  9. 友好简单的部署方式,提供管理页面
  10. -
  11. 还可以配合其他的健康管理工具进行监控,比如 Prometheus
  12. -
-

安装

-

由于 MinIO Server 已经提供了 Docker 的安装镜像,那我们就以 Docker 安装为例,其他安装方式可参考官方教程 MinIO Quickstart Guide

-

关于 Docker 的安装这里不再赘述,Docker 相关详细的使用等知识,可参考我之前的文章 Docker(一)

-
1
2
3
4
5
6
7
8
# 1. 拉取 minio docker 镜像
docker pull minio/minio
# 2. 运行 minio 服务
docker run -p 9000:9000 --name minio \
-v /opt/docker/minio/data:/data \
-v /opt/docker/minio/config:/root/.minio \
-d --restart=always \
-d minio/minio server /data
-
-

这里简单说一下命令的含义,应用命名为 minio ,运行服务在 9000 端口,同时将容器的相关路径文件映射到宿主机的 /opt/docker/minio 路径,开机自启

-
-

成功运行服务,可查看日志

-
1
2
3
4
5
6
7
8
9
10
Endpoint: http://172.17.0.2:9000 http://127.0.0.1:9000
Browser Access:
http://172.17.0.2:9000 http://127.0.0.1:9000
Object API (Amazon S3 compatible):
Go: https://docs.min.io/docs/golang-client-quickstart-guide
Java: https://docs.min.io/docs/java-client-quickstart-guide
Python: https://docs.min.io/docs/python-client-quickstart-guide
JavaScript: https://docs.min.io/docs/javascript-client-quickstart-guide
.NET: https://docs.min.io/docs/dotnet-client-quickstart-guide
Detected default credentials 'minioadmin:minioadmin', please change the credentials immediately using 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD'
-

安装完成后,我们就可以通过 http://localhost:9000 访问 MinIO 服务,默认用户名和密码分别为: minioadmin, minioadmin

-

使用

-

页面操作

-

-

我们直接看图,输入账号密码后,可以看到 MinIO 的管理页面,我们就可以上传文件,是不是很方便。第一次上传必须先要创建一个 bucket 后,才可以上传,如下图操作结果

-

-

Client 操作

-

SDK 操作

-

这里以 Java 语言为例,查看官方文档时,一定要查看英文文档,中文文档已年久失修落后很多,其他的语言实现请参考官方文档

-
-

导入依赖

-
1
2
3
dependencies {
implementation "io.minio:minio:8.1.0"
}
-

功能实现

-

由于我这里是 SpringBoot 项目,为了方便在应用的 application.yml 文件中配置了 MinIO 相关的参数

-

配置文件

-
1
2
3
4
5
6
7
8
9
10
11
minio:
# minio 服务运行的地址
endpoint: http://127.0.0.1
# minio 服务运行的端口
port: 9000
# minio 服务登录账号
accessKey: minioadmin
# minio 服务登录密码
secretKey: minioadmin
# minio 设置上传默认存放桶
bucketName: cpe-manager-test
-

工具类

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/**
* 资源上传工具类
*
* @author : Jerry xu
* @since : 2021/3/18 14:05
*/
@Slf4j
@Component
public class MinioUtils {

/**
* minio:
* endpoint: http://192.168.1.163
* port: 9000
* accessKey: minioadmin
* secretKey: minioadmin
* bucketName: cpe-manager-test
*/
@Value("${minio.endpoint}")
private static final String ENDPOINT = "http://192.168.1.163";
@Value("${minio.port}")
private static final Integer PORT = 19000;
@Value("${minio.accessKey}")
private static final String ACCESS_KEY = "minioadmin";
@Value("${minio.secretKey}")
private static final String SECRET_KEY = "minioadmin";
@Value("${minio.bucketName}")
private static final String BUCKET_NAME = "cpe-manager-test";

private static MinioClient minioClient;

public static MinioClient getInstance() {
if (minioClient == null) {
minioClient = MinioClient.builder().endpoint(ENDPOINT, PORT, false).credentials(ACCESS_KEY, SECRET_KEY).build();
}
return minioClient;
}

/**
* 获取minio所有的桶
*
* @return java.util.List<io.minio.messages.Bucket>
* @throws Exception exception
*/
public static List<Bucket> getAllBucket() throws Exception {
// 获取minio中所以的 bucket
List<Bucket> buckets = getInstance().listBuckets();
for (Bucket bucket : buckets) {
log.info("bucket 名称: {} bucket 创建时间: {}", bucket.name(), bucket.creationDate());
}
return buckets;
}

/**
* 将图片上传到minio服务器
*
* @param inputStream 输入流
* @param objectName 存储的文件名称,必须包含后缀
* @param bucketName 自定义存储桶
*/
public static String uploadToMinio(InputStream inputStream, String objectName, String bucketName) {
try {
// 获取文件后缀
String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf("."));
String contentType = FileType.getContentType(fileSuffix);
// // 重新生成文件名,避免重复
// String objectName = UUID.randomUUID().toString() + fileSuffix;
long size = inputStream.available();
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(inputStream, size, -1)
.contentType(contentType)
.build();
// 上传到minio
ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs);
inputStream.close();
if (!StringUtils.isEmpty(objectWriteResponse.etag())) {
// 返回上传获取到的地址
return getUrlByObjectName(objectName);
}
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 将图片上传到minio服务器,默认存放在 cpe-manager-test 桶内
*
* @param inputStream 输入流
* @param objectName 存储的文件名称,必须包含后缀
*/
public static String uploadToMinio(InputStream inputStream, String objectName) {
try {
// 获取文件后缀
String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf("."));
String contentType = FileType.getContentType(fileSuffix);
// // 重新生成文件名,避免重复
// String objectName = UUID.randomUUID().toString() + fileSuffix;
long size = inputStream.available();
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(objectName)
.stream(inputStream, size, -1)
.contentType(contentType)
.build();
// 上传到minio
ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs);
inputStream.close();
if (!StringUtils.isEmpty(objectWriteResponse.etag())) {
// 返回上传获取到的地址
return getUrlByObjectName(objectName);
}
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 根据指定的objectName获取下载链接,需要bucket设置可下载的策略
*
* @param objectName 对象的名称
* @return java.lang.String
*/
public static String getUrlByObjectName(String objectName) {
try {
return getInstance().getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(BUCKET_NAME)
.object(objectName)
// 过期策略【默认有效期7天】
// .expiry(2, TimeUnit.HOURS)
.build());
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 根据objectName从minio中下载文件到指定的目录
*
* @param objectName minio上的文件名称
* @param fileName 下载生成的文件名
* @param dir 文件目录
* @throws Exception exception
*/
public static void downloadFromMinioToFile(String objectName, String fileName, String dir) throws Exception {
GetObjectArgs objectArgs = GetObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(objectName)
.build();
File file = new File(dir);
if (!file.exists()) {
if (file.mkdirs()) {
log.error("创建失败");
}
}
InputStream inputStream = getInstance().getObject(objectArgs);
FileOutputStream outputStream = new FileOutputStream(new File(dir, fileName.substring(fileName.lastIndexOf("/") + 1)));
int length;
byte[] buffer = new byte[1024];
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
outputStream.close();
inputStream.close();
}

/**
* 根据文件名批量删除(默认删除 BUCKET_NAME 下的文件)
*
* @param listFile 文件名(含后缀)列表,例如:demo.png
* @return 成功返回为null, 失败返回Map<objectName, failMessage>
*/
@SneakyThrows
public static Map<String, String> removeObjects(List<String> listFile) {
List<DeleteObject> objects = new LinkedList<>();
Map<String, String> resultMap = new HashMap<>();
listFile.forEach(t -> objects.add(new DeleteObject(t)));
Iterable<Result<DeleteError>> results =
getInstance().removeObjects(
RemoveObjectsArgs.builder()
.bucket(BUCKET_NAME)
.objects(objects)
.build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
resultMap.put(error.objectName(), error.message());
log.error("Error in deleting:{}, message{}", error.objectName(), error.message());
}
return resultMap;
}

}
-

上传接口

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation(value = "文件上传", notes = "支持多文件上传")
public List<String> uploadTest(@ApiParam(value = "文件") @RequestParam("file") List<MultipartFile> file) {
// 上传的图片地址
List<String> successFile = new ArrayList<>(file.size());
file.forEach(t -> {
try {
String url = MinioUtils.uploadToMinio(t.getInputStream(), t.getOriginalFilename());
log.info("图片地址{}", url);
successFile.add(url);
} catch (IOException e) {
e.printStackTrace();
}
});
return successFile;
}

-

测试

-

问题

-

bucket命名

-

创建 bucket 时,命名不可以使用下划线符号 “_

-

账号密码修改

-

通过网页管理页面修改登录账号及密码,提示 “Credentials of this user cannot be updated through MinIO Browser.” ,原因是安装应用时,并未显示的指定用户名和密码,可在运行启动时添加如下配置

-
1
2
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \
-

最长7天有效

-

通过网页管理页面共享图片或者是使用 SDK 上传图片得到的图片 URL 地址,有效期最长为7天

-
1
2
3
4
5
6

mc config host add minio http://192.168.1.163:19000 minioadmin minioadmin --api S3v4
mc policy set public minio/cpe-manager-test

mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin --api S3v4
mc policy set public minio/bucket (bucket修改成你自己的名字)
-

图片无法查看

-
    -
  1. 使用 SDK 上传时,需要注意设置content-type信息
  2. -
  3. 无权限查看
  4. -
-

小结

-

关于 MinIO 还有很多知识点,本片只是站在使用者角度,把一些使用过程和问题进行了汇总,谈不上深度

-

参考

-
    -
  1. Minio 手册
  2. -
  3. Minio 示例
  4. -
  5. Minio 修改密码
  6. -
  7. Minio
  8. -
  9. Minio 安装以及使用
  10. -
  11. Minio 设置文件链接永久有效
  12. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2021/03/27/oss/index.html b/2021/03/27/oss/index.html deleted file mode 100644 index 43f1e1ce9..000000000 --- a/2021/03/27/oss/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -OSS 初体验 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- OSS 初体验 -

- - -
- - - - -

在之前 SpringBoot(十二)文件上传 文章中,已经学习了使用 SpringBoot 基础的功能,完成静态资源的管理,本片文章我们同样也是对非结构化的静态数据进行管理,不过这次我们使用的是比较常用的 OSS 服务,废话不说,我们一起开始 OSS 之旅吧

- -

什么是 OSS

-

OSS 是一种面向海量数据规模的分布式存储服务,具有稳定,可靠,安全,低成本的特点。主要用来存储各种非结构化的数据,比如视频,图像,日志,文本文件等。OSS 服务提供标准的 RESTful API 接口,并提供一些常用语言的 SDK 包,方便开发者进行快速开发和二次处理

-

常用的 OSS

-

市面上提供云服务的厂商有很多,这里以阿里云的 OSS 服务为主来,完成 OSS 相关的学习和实践

-

依赖

-
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.aliyun.oss/aliyun-sdk-oss -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.11.1</version>
</dependency>
-

OSS 工具类

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* OSS 文件上传
*
* @author : Jerry xu
* @since : 2020/11/3 09:12
*/
@Slf4j
public class OssUtils {

// 访问域名
private static final String ENDPOINT = "xxxxx";
// 存储空间
private static final String BUCKET_NAME = "xxxxx";
//==================访问密钥==================
private static final String ACCESS_KEY_ID = "xxxxx";
// 用户用于加密签名字符串和OSS用来验证签名字符串的密钥,必须保密
private static final String ACCESS_KEY_SECRET = "xxxxx";

/**
* 通过文件上传图片
*
* @param file 文件
* @return 上传结果地址
*/
public static String uploadFileByFile(File file) {
// // NIO 方式
// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());
// // 其他方式
// byte[] fileByte = Files.readAllBytes(Paths.get(file.getPath()));
// return uploadFileByByte(fileByte, file);
URL url;
String urlStr = null;
String[] split = new String[0];
try {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
// 获取文件名
String fileName = file.getName();
ossClient.putObject(BUCKET_NAME, fileName, file);
// 设置过期时间
url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, new Date(System.currentTimeMillis() + 3600 * 24 * 365 * 10));
log.info("原始图片地址:{}", url);
urlStr = url.toString();
// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());
// PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, "test", new ByteArrayInputStream(fileByte));
// // 不设置过期时间
// PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);
// // 去除过期时间参数地址
// split = urlStr.split("\\?");
} catch (Exception e) {
log.warn(e.getMessage());
}
return urlStr;
}

/**
* 通过字节数组上传图片
*
* @param binaryBytes 字节数组
* @param fileName 文件名
* @return 上传结果地址
*/
public static String uploadFileByByte(byte[] binaryBytes, String fileName) {
InputStream inputStream = new ByteArrayInputStream(binaryBytes);
return uploadFileByInputStream(inputStream, fileName);
}

/**
* 通过输入流上传图片
*
* @param inputStream 输入流
* @param fileName 文件名
* @return 上传结果地址
*/
public static String uploadFileByInputStream(InputStream inputStream, String fileName) {
URL url;
String urlStr = null;
String[] split = new String[0];
try {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
// String fileName = UUID.randomUUID().toString() + ".jpeg";
ossClient.putObject(BUCKET_NAME, fileName, inputStream);
// 设置过期时间
url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, new Date(System.currentTimeMillis() + 3600 * 24 * 365 * 10));
log.info("原始图片地址:{}", url);
urlStr = url.toString();
// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());
// PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, "test", new ByteArrayInputStream(fileByte));
// // 不设置过期时间
// PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);
// // 去除过期时间参数地址
// split = urlStr.split("\\?");
} catch (Exception e) {
log.warn(e.getMessage());
}
return urlStr;
}

}
-

上传

-

这里我们写一个上传接口

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ApiOperation("文件上传", notes = "支持多图上传")
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public List<String> uploadTest(@RequestParam("file") List<MultipartFile> file) {
List<String> uploadList = new ArrayList<>(file.size());
file.forEach(t -> {
try {
String url = OssUtils.uploadFileByInputStream(t.getInputStream(), t.getOriginalFilename());
uploadList.add(url);
} catch (IOException e) {
e.printStackTrace();
}
});
return uploadList;
}
-

测试

-

不废话了,直接看图就好了
-

-

问题

-
    -
  1. 对于上传获取到的文件地址是一个会过期的地址,并不是一个固定不变的地址,如上截图所示,我偷懒直接将地址链接出的相关参数删去,拿到了一个永久存储的访问连接地址。但这里需要注意,这需要在你的 OSS 管理后台去设置你的文件存储的过期策略。这里就不进行截图演示了(主要是我没有登录系统的账号密码,逃 ~)
  2. -
  3. 对于上传的文件我没有自定义文件名,这里有个问题是当用户上传 OSS 服务中已经存在的文件名的文件时,新上传的会覆盖旧文件,因此这个地方需要根据实际的业务场景选择合适的方式。在 OssUtils 工具类中我已经注释掉了将文件名重命名的代码,你可以在此处按照你的业务进行更改
  4. -
  5. 第三个问题就是结合上面的两点的汇总方案,其实呢,对于一般的系统,这些静态资源就存永久的连接地址即可。但目前新的系统对用户的资料等也有了 “稍微” 高一点的保护,就是这些资源都是有时效性的,获取的地址就是我们上传拿到的原始地址,而我们存放在数据库中当然也不会是之前那种永久的连接地址,而是对应图片的一个唯一标识信息(可以是重命名后的文件名或者其他能够唯一标识资源你的字段),然后用户访问这些资源时,用存放在数据库中的唯一标识去 OSS 服务上查询对应的资源,然后加载这个地址去显示。
  6. -
-

参考

-
    -
  1. 阿里云对象存储 OSS
  2. -
  3. 对象存储 Kodo
  4. -
  5. 华为云对象存储服务 OBS
  6. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2021/05/05/freedom-pact/index.html b/2021/05/05/freedom-pact/index.html deleted file mode 100644 index fda277d9c..000000000 --- a/2021/05/05/freedom-pact/index.html +++ /dev/null @@ -1,589 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -评论不自由,赞美无意义 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 评论不自由,赞美无意义 -

- - -
- - - - -

就像文章标题所述。每到三,五月这个时间,网络变得异常脆弱。各种“奇怪”的网站访问起来很费劲。对于一个技术人员,这些问题可以解决,但是每次都需要花费一定的时间和精力来应对这些,而且随着 GFW 的不断升级和加强,应对的策略和技术也是在不断迭代,前前后后已经出现了多种技术,本篇就以这个契机,梳理截止到 2021-05-05 所了解到关于 “代理” 相关的知识。现在主流的科学上网技术有 VPN、SS、SSR、V2Ray、Trojan、Trojan-Go,小众的 WireGuard、Brook、Snell 和 NaiveProxy 等

- -

本篇文章大量使用了 一灯不是和尚 作者发布 《科学上网工具哪个好? 》文章中的内容,在此感谢作者对各技术的汇总以及经验总结,本篇文章是在原文章的基础上进行的扩展补充

-
-

VPN

-

虚拟专用网络(Virtual Private Network,缩写:VPN)是常用于连接中,大型企业或团体间私人网络的通讯方法。它利用隧道协议(Tunneling Protocol)来达到发送端认证,消息保密与准确性等功能。

-

VPN 只是一个统称,它有多种具体实现。比如:

-
    -
  • PPTP(点对点隧道协议:Point to Point Tunneling Protocol);
  • -
  • L2TP(第二层隧道协议:Layer Two Tunneling Protocol);
  • -
  • IPsec(互联网安全协议:Internet Protocol Security);
  • -
  • WireGuard(一种协议);
  • -
  • OpenVPN(一种虚拟专用网络 Virtual Private Network(VPN)系统);
  • -
  • IKEv2(因特网密钥交换:Internet Key Exchange) 等
  • -
-

WireGuard

-

WireGuard 是由 Jason A. Donenfeld 开发,是最新的协议实现(在 2020 年,WireGuard 协议已被添加到 Linux 和 Android 内核中,从而为 VPN 提供商所采用。默认情况下,WireGuard 使用 Curve25519 进行秘钥交换,并使用 ChaCha20 进行加密,但还具有客户端和服务器之间预共享对称秘钥的功能)

-

OpenVPN

-

OpenVPN 是由 James Yonan 编写,实现了在路由或桥接配置和远程访问设施中创建安全点对点或站点对站点连接的技术。它实现了客户端和服务器应用程序。它不与IPsec兼容

-

OpenVPN 允许对等方使用预先共享的密钥、证书或用户名/密码相互验证。当在多客户端服务器配置中使用时,它允许服务器使用签名和证书颁发机构为每个客户端发布身份验证证书。

-

SS

-

SS 是 Shadowsocks 的缩写,中文名为影梭,为了避免关键词过滤,网友喜欢将 Shadowsocks 称为 “酸酸”,是一种基于 Socks5 代理方式的加密传输协议,也可以指定实现这个协议的各种开发包。Shadowsocks 是由 Clowwindy 为了自己使用谷歌查资料而编写;Shadowsocks 分为服务端和客户端,在使用之前,需要先将服务器端程序部署到服务器上面,然后通过客户端连接并创建本地代理。后来,他觉得这个东西非常好用,速度也很快,于是将源码提交到了 GitHub。由于其优秀的使用体验,Shadowsocks 被广泛传播,导致作者被某部门请去 “喝茶”。迫于压力 Clowwindy 于 2015-08-22 宣布停止维护此项目,并移除其个人页面所存储的源代码,而且保证永不再参与维护更新

-

虽然 Clowwindy 被迫放弃了 Shadowsocks,但开源界没有放弃,各路大神依旧在为 Shadowsocks 添砖加瓦,这就是开源的力量,倒下一个 Clowwindy,会有千千万万个 “Clowwindy” 站出来

-

SSR

-

SSR 是 ShadowsocksR 的缩写,网名爱称 “酸酸乳”,是在 Shadowsocks 的作者被请去喝茶之后,网名为 breakwa11 的用户发起的 Shadowsocks 的一个分支版本,它在 Shadowsocks 的基础上增加了一些数据混淆方式,修复了部分安全问题并提高了 QoS 优先级。由于 ShadowsocksR 在协议和混淆方面做了改进,更加不容易被 GFW 检测到,而且兼容原 Shadowsocks,并为新项目命名为 Shadowsocks-R,一开始部分代码由社区人员进行更新。由于不完全开源,也导致后来使用 SS 和 SSR 的用户分为两个阵营,互相撕逼,直到开发者 breakwa11 被人肉出来。breakwa11 最终决定删除 Shadowsocks-R 项目的所有代码,并解散了所有相关群组

-

事件始末澄清

-

ShadowsocksR 的作者一开始曾有过违反 GPL 协议,在发布二进制文件时不开放源码的争议。不过后来 Shadowsocks-R 项目由 breakwa11 采用了与 Shadowsocks 相同的 GPL、Apache、MIT 等多重自由软件许可协议

-
    -
  • 2017-07-19,ShadowsocksR 作者 breakwa11 在 Telegram 频道 ShadowsocksR news 里转发了深圳市启动 SS 协议检测的消息并被大量用户转发,在电报(TG)圈引发恐慌。
  • -
  • 2017-07-24,breakwa11 遭到自称 “ESU.TV” 的不明省份人士人身攻击,对方宣传如果不停止开发并阻止用户讨论此事件将发布更多包含个人隐私的资料,随后 breakwa11 表示遭到对方人肉搜索并公开个人资料。为防止对方继续伤害无关人士,breakwa11 删除了 GitHub 上的所有代码、解散相关交流群组,并停止 ShadowsocksR 项目
  • -
-

从本质上来说,Shadowsocks 与 ShadowsocksR 的基本原理相同,都是基于 Socks5 的代理工具,只在本地客户端和服务器对数据包加解密,然后使用 Socks5 协议转发加密的数据包,而不用在乎使用什么协议,所以 Socks5 代理比其他应用层代理速度要快的多

-

Socks5

-

这里顺带科普一下 Socks5,Socks5 代理的原理是把你的网络数据请求先发送到你的代理服务器,然后由代理服务器转发给目标;如果目标有反馈发送到代理服务器,那么代理服务器会将数据包直接传回到你的本地网络,整个过程只需要数据的二次传输,并没有额外的处理。

-
-

示例:现在呢在深圳,你的代理服务器在香港,如果你想要访问 Google,那么你首先需要把数据请求通过本地 Socks5 代理客户端发给在你在香港的服务器上的 Socks5 代理服务端,然后你在香港的服务器将数据请求发送给 Google,再把 Google 反馈的结果传到你香港的代理服务器,然后通过 Socks5 服务端回传到本地的 Socks5 客户端,这样就可以绕开 GFW 的检测而实现科学上网

-
-

显而易见,Socks5 代理的所有数据走的任然是公网,而且在公网传输过程中,没有对数据进行任何加密和混淆,这跟 VPN 在公网建立虚拟专用通道传输过程中,对数据高强度加密的方式完全不同。Shadowsocks 和 ShadowsocksR 只在客户端和服务器端对数据做了简单加密和认证,主要功能是流量转发,过强才是主要目的。虽然 ShadowsocksR 已经停止更新很久了,而 Shadowsocks 仍处于社区人员的更新和维护之中,不断修复漏洞并增加新功能,所以现在 Shadowsocks 比 ShadowsocksR 更强大

-

提醒

-

不要迷信 SSR 一定比 SS 强,也包括现在的 V2Ray,Trojan,甚至是 WireGuard 等,因为增加混淆意味着损失速度,混淆加密越是强悍,那么其速度和稳定性损失就越大,另外 SSR 至今已被研究透了,而且长时间没有更新维护,其流量特征是可以被 GFW 精准识别,所以用 SSR 和 SS 没有本质区别,由于 SS 一直更新维护,反而更稳定。

-

V2Ray

-

V2Ray 是在 Shadowsocks 被封杀后,为表示抗议而开发,属于后起之秀,功能更加强大,为抗 GFW 封锁而生。V2Ray 现在已经是 Project V 项目的核心工具。而 Project V 是一个平台,其中也包括支持 Shadowsocks 协议。由于 V2Ray 早于 Project V 项目,且名声更大,我们习惯称 Project V 项目为 V2Ray,所以我们平常所说的 V2Ray 其实就是 Project V 这个平台,也就是一个工具集。其中,只有 VMess 协议是 V2Ray 社区原创的专属加密通讯协议

-

V2Ray 目前支持一下协议(截止 2019-12)

-
    -
  • Blackhole: 中文名称“黑洞”,是一个出站数据协议,它会阻碍所有的数据的出站,配合路由(Routing)一起使用,可以访问被封杀的网站
  • -
  • DNS: 是一个出站协议,主要用于拦截和转发 DNS 查询。此出站协议只能接收 DNS 流量(包含基于 UDP 和 TCP 协议的查询),其它类型的流量会导致错误。在处理 DNS 查询时,此出站协议会将 IP 查询(即 A 和 AAAA)转发给内置的 DNS 服务器。其它类型的查询流量将被转发至原本的目标地址,DNS 出站协议在 V2Ray 4.15 中引入
  • -
  • Dokodemo-door: 中文名称“任意门”,是一个入站数据协议,它可以监听一个本地端口,并把所有进入此端口的数据发送到指定服务器的一个端口,从而达到端口映射的效果
  • -
  • Freedom: 是一个出站协议,可以用来向任意网络发起(正常的)TCP 或 UDP 数据
  • -
  • HTTP: 超文本传输协议,是传统的代理协议
  • -
  • Socks: 标准的 Socks 协议实现,兼容 Socks 4Socks 4aSocks 5『🙃目前无法正常访问』,也属于是一种传统的代理协议
  • -
  • VMess: 是 V2Ray 专用的加密传输协议,它分为入站和出站两部分,通常作为 V2Ray 客户端和服务器之间的桥梁。因为增加了混淆和加密,据说比 Shadowsocks 更安全。VMess 依赖于系统时间,请确保使用 V2Ray 的系统 UTC 时间误差在 90 秒之内,与时区无关。在 Linux 系统中可以安装 ntp 服务来自动同步系统时间
  • -
  • Shadowsocks: 包含入站,出站两部分协议,兼容大部分其它版本的实现。最早被个人开发的科学上网梯子协议,但 V2Ray 目前不支持 ShadowsocksR
  • -
  • Trojan: 特洛伊木马服务器如何对有效的特洛伊木马协议和其他协议(可能是 HTTPS 或任何其他探针)做出反应
  • -
  • VLESS: 是一个无状态的轻量传输协议,它分为入站和出站两部分,可以作为 V2Ray 客户端和服务器之间的桥梁,与 VMess 不同,VLESS 不依赖与系统时间,认证方式同样为 UUID,但不需要 alterId
  • -
  • Loopback: 是一个出站协议,可使出站连接被重新路由,最低支持版本 v4.36.0+
  • -
-

截止到 2021-05,V2Ray 可选的传输层配置有:TCP、mKCP,WebSocket、HTTP/2,DomainSocket、QUIC、gRPC。其中 mKCP、QUIC 和 TCP 用于优化网络质量;WebSocket 用于伪装;HTTP/2 和 DomainSocket 用于传输以及 TLS 加密

-

V2Ray 不仅可以在传输层配置 TLS 使用 HTTP 和 Socks 变成 HTTPS 和 Socks over TLS 协议,也可以使用 MTProto、Shadowsocks 和 VMess 通过传输层配置 TLS 加密伪装成 TLS 流量。所以,VMess 配置是 TLS 加密的最常见的做法,但没人会对 Shadowsocks 使用 TLS 加密,因为完全没有意义

-

客户端

-
-

V2Ray 客户端

-
-

Xray 与 XTLS

-

Xray 与 V2Ray 完全类同,Xray 是 Project X 项目的核心模块。因为 Xray 和 XTLS 黑科技 的作者 rpfx 曾是 V2fly 社区的重要成员,所以 Xray 直接 fork 全部 V2Ray 的功能,然后进行性能优化,并增加了新的功能,使 Xray 在功能上成为了 V2Ray 的超集,且完全兼容 V2Ray。

-

简而言之,Xray 是 V2Ray 的项目分支,Xray 是 V2Ray 的超集,就跟 Trojan-Go 和 Trojan-GFW 的关系类似,而且 Xray 性能更好,速度更快,更新迭代也更频繁。

-
-

由于 V2Ray-core 4.33.0 版本起,删除了 XTLS 黑科技,但任然支持 VLESS,所以是否原生支持 XTLS 是 Xray 和 V2Ray 最大的区别之一

-
-

Trojan 与 Trojan-Go

-

Trojan 原特指特洛伊木马,是一种计算机病毒程序。但是,我们今天所说的 Trojan 是一种新型的科学上网技术,全称为 Trojan-GFW,是目前最成功的科学上网伪装技术之一。你可以认为 Trojan 是 V2Ray 的 “WS + TLS” 模式的精简版,速度比 V2Ray 更快,伪装比 V2Ray 更逼真,更难以备 GFW 识别

-

Trojan 工作原理:Trojan 通过监听 443 端口,模仿互联网上最常见的 HTTPS 协议,把合法的 Trojan 数据伪装成正常的 HTTPS 通信,并真正地完整完成 TLS 握手,以诱骗 GFW 认为它就是 HTTPS,从而不被识别。Trojan 处理来自外界的 HTTPS 请求,如果是合法的,那么为该请求提供服务,否则将该流量交给 Caddy、Nginx 等 Web 服务器,由 Caddy、Nginx 等为其提供网页访问服务。基于整个交互过程,这样能让你的 VPS 更像一个正常的 Web 服务器,因为 Trojan 的所有行为均为 Caddy、Nginx 等 Web 服务器一致,并没有引入额外特征,从而达到难以识别的效果

-

Trojan-Go 是 Trojan-GFW 的分支项目,对 Trojan 进行性能优化,并增加不少新特性,Trojan-Go 性能和功能均有大幅度的提升,而且支持分流和 CDN

-

总结

-

通过上述,以及其他信息来源对这些技术有了一定的基本认识,从以下的几个方面来进行总结

-

原理不同

-

VPN:强调对公网传输过程中数据的加解密
-SS/SSR/V2Ray/Xray/Traojan:专注于在客户端和服务器间加密,公网传输过程中特征没有 VPN 明显

-

目的不同

-

VPN:是走在公网中自建的虚拟专用通道,使用强大的加解密算法,为数据传输安全性、私密性而生,被广泛应用于企业、高校、科研部门等远程数据传输领域
-SS/SSR/V2Ray/Xray/Trojan/Trojan-Go:是为了数据能够安全通过 GFW 而生,更强调的是对数据的混淆和伪装,加解密只是为了更好的隐藏数据特征而顺利通过 GFW 的检测

-

项目诞生的大致顺序

-

VPN > SS > SSR/V2Ray/WireGuard > Trojan/Trojan-Go > Xray

-

致每一个追求真理的人

-

凡事都有两面性,看如何去看待。其实我一直认为,有墙确实是一件好事,毕竟祖国互联网发展也不过 20 多年,在一定程度上过滤掉了一些没有自我认知,自我思考盲目跟风,觉得国外的月亮圆,国外什么都好的一群人;保障了在互联网开始萌芽的本土企业(毕竟国外的产品和技术都已经发展了好几轮,从解决问题的能力,使用的用户体验(不含本地化)等等方面都是完全甩开本地企业的服务)发展,提供给企业和网民一起成长的安全环境,在这一点上 GFW 功不可没。但作为一个技术从业人员来说,其中大部分的技术理念思想,技术平台等纯技术领域相关的东西来说不是那么友好,常常伴随着由于网络原因而造成的各种乱七八糟的问题,对于暂无本土相关替代服务支持时,在一定程度上阻碍了国内相关技术的发展进度和创新。

-

真如前面所述,凡事都有两面性。对于未知的事物,人类本能的会产生恐惧,因为它不可控,而互联网正真不可控的不是技术而是人,人是复杂的个体,一个技术的好坏不是它本身,而是使用的人在做什么样的事,而做的事在一定的环境下它是有好坏之分的。因此我在这里对自我约束,仅为获取相关学习的知识,不参与散布谣言、政治相关等言论,踏踏实实搞技术

-
-

希望未来的有一天,国内技术人员的输出是全球技术的风向标

-
-

参考

-
    -
  1. 试用 Always Free 云服务
  2. -
  3. 申请 Oracle Cloud 永久免费服务
  4. -
  5. 2021年申请永久免费甲骨文云 Oracle Cloud 并创建实例最全攻略
  6. -
  7. 多种科学上网指导教程汇总
  8. -
  9. V2ray XTLS黑科技
  10. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2022/06/05/top1/index.html b/2022/06/05/top1/index.html deleted file mode 100644 index 02b664e65..000000000 --- a/2022/06/05/top1/index.html +++ /dev/null @@ -1,506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -重要说明 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 重要说明 -

- - -
- - - - -

距离上一次更新文章已经过去了 1 年多了,时间可过的真快。原计划将现在的主站点要进行按照领域划分,将平时工作中遇到的、实践的技术整理到对应的领域,方便快速查找,提供一个沉浸式的学习知识体验,但由于个人也是一个懒癌拖更患者,加之在过去的一年工作中处在新领域,学习了很多技术,很多笔记还没有整理完善,因此没有及时更新博客,同时最近也遇到了站点无法正常访问,经过一系列的排查,发现了是由于开源的 CDN 提供方 jsdelivr 被污染了 DNS,因此不得不先更新站点的基础服务,以便能正常访问

- -
-

话说,国内的网络环境真的是一言难尽,不得不说在技术、技术基础设施、技术思想等发展道路上任重道远

-
-

对于个人几个主要的站点,规划如下

-
    -
  1. incoder.org: 作为分享生活、感悟、个人状态为主的地方,偶尔汇总某些技术等的综合性文章
  2. -
  3. backend.incoder.org: 记录以 Java 为基础的后端开发生态技术领域
  4. -
  5. mobile.incoder.org: 记录以原生开发为基础的移动端开发生态技术
  6. -
  7. incoder.app: 记录个人开源应用
  8. -
-

后续会迁移本站点部分文章到具体的领域站点

-
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2022/06/15/granddad/index.html b/2022/06/15/granddad/index.html deleted file mode 100644 index 1129f589e..000000000 --- a/2022/06/15/granddad/index.html +++ /dev/null @@ -1,491 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -愿天堂没有病痛 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- 愿天堂没有病痛 -

- - -
- - - - -

仅以此篇文章记录,我对爷爷的思念,愿天堂没有病痛……

- -

时间仿佛定格在这一天了,一向最不留情面的爷爷,在寂静的夜晚,悄悄地离开了我们。虽然对于爷爷感情并不是很深,但或多或少在我心里有不曾抹去的对与我的爱,也许只是在我看来是那么的保守和不近人情,可能这只是您独有的关爱方式罢了。

-

不经想起上一次见面,是在上一次回家(2020 年劳动节假期),去探望了您,没想到这一见面竟然是天人永隔。您的连环追命问题,迫使我是连连败退,不敢轻易登上您的大门。听爸妈说,今年您身体不好,一直住在医院,原本计划等今年十一左右回家,然后再去看看您,这成了永远无法完成的计划😭。姐姐哭着告诉我想到的事情就要去做,不要等到了失去后才后悔莫及,是呀,人总是等到失去后才懂得珍惜,我只是感觉越是长大越是难以想到同时也能做到

-

爷爷在那个时代属于中等个头,身材在我看来不胖不瘦,家里有琳琅满目各式各样稀奇古怪的东西,有手动给木头打孔的转,刨木头的刨子,标记线的墨斗还有好多好多我不知道的工具一副活脱脱的木工装备,但实际上爷爷并不是木工,只知道爷爷是有单位的人,是有文化的知识分子,在县城有单位分房子,在我印象中有三件事我记忆深刻

-

第一件,小时候去您家玩,在桌子上看到了各式各样的桥梁图纸,不知道这些桥梁都修建在哪里,然后看到了有一个白色透明很高级的三角板,当然我也见到了不一样的东西,一种被称之为 “雷管” 的炸药,这些应该都是您当年工作中经常使用的一些工具吧,在现在来看应该属于土木工程系,后来上小学,那把三角尺成了小学生涯最长的使用工具。如果有时光机,我想听一听您那时候工作故事和那些作图技巧,还想去看看那些您曾经设计或建造的桥梁道路

-

第二件,有一年过春节,爸爸给我买了枪(那个时候,男孩子过年好像都少不了这个礼物),然后到您的住的地方,您说这个样式不好,一连就在您家前门店家那里换了 3 次,我现在也记不清那是一把什么样的枪,好像记得一按斑鸠就会有音乐,在那个时候很高级的样子,如果有时光机,我想把那是我的玩具收藏进我的百宝箱里

-

第三件,那时候我小学六年级还是刚上初中吧。那一年大姑,小姑带着弟弟,妹妹从上海回到老家,然后在我们在您的台球厅玩耍,我学会了小台球的游戏规则,没人的时候我们会一起玩耍,可我重来都没有赢过一场,看来我对这项运动没有任何天赋,如果有时光机,我想和您再打一场台球

-

从我的视角来看,随着慢慢的长大到工作,相见机会越来越少,有时候一年也没有见上一面。刚开始工作后偶尔会打电话给你,到后来几乎没有再打过电话给你,因为每次电话我都是扯着嗓子在那喊,你说你才能听清我在说什么,其次本身自己是一个内向的人,话也很少,平时也没啥话可说,就这样渐渐几乎是失去了联系,每次也都是听父母的口中知道您的近况

-

虽然在我的记忆里除了这些温暖的画面,还有不少您的负面形象,可现在这些都不重要了,毕竟这些也是你生命里的一部分。我没有经历过,我没有发言权,就让这些负面的内容随风而去吧。记着一个人的好好过记住一个人的坏,身在异地他乡的我不能及时的回到你身边为你送上最后的一程,深感惭愧😮‍💨

-

爷爷您一路走好,愿天堂里没有病痛

- -
- - - - - -
-
-
请我一杯咖啡吧!
- -
-
- Jerry Xu 微信 - 微信 -
-
- Jerry Xu 支付宝 - 支付宝 -
- -
-
- - - -
-
    -
  • - 本文作者: Jerry Xu -
  • -
  • - 本文链接: - https://incoder.org/2022/06/15/granddad/ -
  • -
  • - 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处! -
  • -
-
- -
- 欢迎关注我的其它发布渠道 - - -
- - - - - -
-
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2024/06/17/git-signature/index.html b/2024/06/17/git-signature/index.html deleted file mode 100644 index c0d2f1316..000000000 --- a/2024/06/17/git-signature/index.html +++ /dev/null @@ -1,672 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Git 签名 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- Git 签名 -

- - -
- - - - -

verified-commit

-

通常 Push 代码到远程托管平台(GitHub,Gitlab,Gitee 等),需要提前在托管平台上传我们 Git 账户的公钥(*.pub),平台使用上传的公钥来验证身份(本地的 Git 私钥与平台上的公钥配对,以确保你有权限读写该仓库),该验证只会在 Push 时进行检查

- -

为什么要签名

-

虽然对 Push 做了检查,但依然不够安全,因为任何拥有该仓库权限的人,都可以在 commit/tag 时使用 git config user.name "假的用户名", git config user.email "假的邮箱地址" 命令来伪造提交者用户信息,这样我们根本无法追溯提交者的身份,所以我们需要给 commit/tag 签名,签名的目的是确保提交的代码在传输和存储过程中没有被篡改,并验证提交者的身份,同时也保证代码的可追溯性

-

commit 签名是在本地,在使用 git commit 命令时进行签名,push 时会将你的签名信息,原封不动 push 到远程仓库

-

commit 签名只是用于验证这条 commit 来自于你本人,与是否有权限操作远程仓库无关

-

如何签名

-

可以使用 GPG、SSH 或 S/MIME,可以在本地对 commit/tag 进行签名。 这些 commit/tag 在 GitHub 上标示为已验证,便于其他人信任更改来自可信的来源

-

SSH、GPG、S/MIME 区别

-
    -
  • SSH 签名是最容易生成的,甚至可以将现有身份验证密钥上传到 GitHub 以用作签名密钥
  • -
  • 生成 GPG 签名比生成 SSH 密钥复杂,但 GPG 具有 SSH 没有的功能,GPG 密钥可以在不使用时过期或撤销
  • -
  • 较大型组织的环境中通常需要 S/MIME 签名
  • -
-

👍 SSH(常用)

-
-

SSH 签名验证需 Git 2.3.4 及以上版本

-
-

检查现有 SSH 密钥

-
1
2
3
# 查看本地 ~/.ssh 路径现有密钥
ls -al ~/.ssh
# 检查输出的列表
-

检查已有密钥列表已有 RSA 密钥,如果你已经有 SHA-2 算法生成 RSA 密钥 那么你可以 跳过 生产新 SSH 密钥,如果没有,为了安全,还是建议重新生成 SHA-2 算法生成 RSA 密钥

-

生产新 SSH 密钥

-
    -
  1. 2022.03.15 -
      -
    • 在该日期 GitHub 删除旧的、不安全的密钥类型来提高安全性
    • -
    • 自该日期起,不再支持 DSA 密钥 (ssh-dss)。 无法github.com 上向个人帐户添加新的 DSA 密钥
    • -
    -
  2. -
  3. 2021.11.02 -
      -
    • 在该日期 之前 带有 valid_after 的 RSA 密钥 (ssh-rsa) 可以继续使用任何签名算法
    • -
    • 在该日期 之后 生成的 RSA 密钥 必须 使用 SHA-2 签名算法。 一些较旧的客户端可能需要升级才能使用 SHA-2 签名
    • -
    -
  4. -
-
-

在生成 SSH 密钥时,我们可以给 SSH 密钥添加密码,也可以不添加密码,这里根据需要选择是否添加密码

-

git-ssh-keygen

-
    -
  1. 执行 ssh-keygen -t ed25519 -C "Jerry.x@outlook.com" 命令,这里的邮箱换成你的 GitHub 邮箱
  2. -
  3. 确认密钥存放位置 -
      -
    • 如果不需调整(默认:/User/blade/.ssh/id_ed25519),可 Enter 键进入下一步
    • -
    • 这里我重命名了密钥 /User/blade/.ssh/id_ed25519_test
    • -
    -
  4. -
  5. 给密钥设置密码 -
      -
    • 如果无需设置,可 Enter 键进入下一步
    • -
    • 如果需设置,输入密码即可
    • -
    -
  6. -
  7. 确认输入的密钥密码,和上一步输入内容一致
  8. -
  9. 提示生成的密钥存放位置和指纹信息
  10. -
-

如果密钥 设置了密码,需要将 SSH 密钥添加到 ssh-agent

-
-

在向 ssh-agent 添加新的 SSH 密钥管理密钥前,应该检查现有 SSH 密钥并生成新的 SSH 密钥

-
-

如果已安装 GitHub Desktop,可使用它克隆存储库,而无需处理 SSH 密钥

-
-
    -
  1. -

    在新的“管理员提升”__ PowerShell 窗口中,确保 ssh-agent 正在运行。 可以使用“使用 SSH 密钥密码”中的“自动启动 ssh agent”说明,或者手动启动它

    -
    1
    2
    3
    # start the ssh-agent in the background
    Get-Service -Name ssh-agent | Set-Service -StartupType Manual
    Start-Service ssh-agent
    -
  2. -
  3. -

    在无提升权限的终端窗口中,将 SSH 私钥添加到 ssh-agent。 如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称

    -
    1
    ssh-add c:/Users/YOU/.ssh/id_ed25519
    -
  4. -
-

将 SSH 密钥添加到该代理时,应使用默认的 macOS ssh-add 命令,而不是使用通过 macports、homebrew 或某些其他外部来源安装的应用程序

-
-
    -
  1. -

    在后台启动 ssh 代理

    -
    1
    2
    $ eval "$(ssh-agent -s)"
    > Agent pid 59566
    -

    根据您的环境,您可能需要使用不同的命令。 例如,在启动 ssh-agent 之前,你可能需要通过运行 sudo -s -H 根访问,或者可能需要使用 exec ssh-agent bashexec ssh-agent zsh 运行 ssh-agent

    -
  2. -
  3. -

    如果你使用的是 macOS Sierra 10.12.2 或更高版本,则需要修改 ~/.ssh/config 文件以自动将密钥加载到 ssh-agent 中并在密钥链中存储密码

    -
      -
    • -

      检查你的 ~/.ssh/config 文件是否在默认位置

      -
    • -
    • -

      如果文件不存在,请创建该文件

      -
    • -
    • -

      打开你的 ~/.ssh/config 文件,然后修改文件以包含以下行。 如果您的 SSH 密钥文件与示例代码具有不同的名称或路径,请修改文件名或路径以匹配您当前的设置

      -
      1
      2
      3
      4
      5
      6
      7
      8
      Host github.com
      # 如果看到了 Bad configuration option: usekeychain 错误,
      # 取消下一行注释,使用 IgnoreUnknown 配置
      # IgnoreUnknown UseKeychain
      AddKeysToAgent yes
      # 如果你选择不向密钥添加密码,应该省略 UseKeychain 行
      UseKeychain yes
      IdentityFile ~/.ssh/id_ed25519
      -
    • -
    -
  4. -
  5. -

    将 SSH 私钥添加到 ssh-agent 并将密码存储在密钥链中。 如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称

    -
    1
    ssh-add --apple-use-keychain ~/.ssh/id_ed25519
    -
  6. -
-

注意

-
-
    -
  1. 当你将 SSH 密钥添加到 ssh-agent 时,--apple-use-keychain 选项会将密码存储在你的密钥链中。 如果选择不向密钥添加密码,请运行命令,而不使用 --apple-use-keychain 选项
  2. -
  3. 选项 --apple-use-keychain 位于 Apple 的 ssh-add 标准版本中。 在 Monterey (12.0) 之前的 macOS 版本中,--apple-use-keychain--apple-load-keychain 标志分别使用语法 -K-A
  4. -
  5. 如果您没有安装 Apple 的 ssh-add 标准版本,可能会收到错误消息。 有关详细信息,请参阅 “错误:ssh-add:非法选项 – apple-use-keychain
  6. -
  7. 如果系统继续提示你输入密码,则可能需要将命令添加到 ~/.zshrc 文件(或 bash 对应的 ~/.bashrc 文件)
  8. -
-
    -
  1. -

    在后台启动 ssh 代理

    -
    1
    2
    $ eval "$(ssh-agent -s)"
    > Agent pid 59566
    -

    根据您的环境,您可能需要使用不同的命令。 例如,在启动 ssh-agent 之前,你可能需要通过运行 sudo -s -H 根访问,或者可能需要使用 exec ssh-agent bashexec ssh-agent zsh 运行 ssh-agent

    -
  2. -
  3. -

    将 SSH 私钥添加到 ssh-agent

    -

    如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称

    -
    1
    ssh-add ~/.ssh/id_ed25519
    -
  4. -
-
-

除了上述的方式生成密钥,还有 为硬件安全密钥生成新的 SSH 密钥 方式,这里不做说明,可以移步官方文档查看

-

将 SSH 密钥添加到 GitHub 账户

-

github-ssh-add

-
-

其他更多设置可参考官方文档 新增 SSH 密钥到 GitHub 帐户

-
-

将签名密钥告诉 Git

-
    -
  • 如需全局配置:添加 --global 参数
  • -
  • 如果你有多个 Git 账号,推荐 按照不同的账号分别进行配置,具体可参考 Git 多账号配置 这篇文章
  • -
-
-
1
2
3
4
5
6
7
8
9
# 1. 配置 Git 使用 SSH 对提交和标记签名
git config gpg.format ssh
# 2. 配置指定 SSH 签名密钥
# 将 ~/.ssh/id_ed25519 替换为要使用的公钥路径
git config user.signingkey ~/.ssh/id_ed25519.pub
# commit 开启自动签名
git config commit.gpgsign true
# tag 开启自动签名
git config tag.gpgsign true
-

配置可信公钥列表

-
1
2
3
4
5
6
7
mkdir -p ~/.config/git
touch ~/.config/git/allowed_signers
# 将指定的密钥文件(~/.ssh/id_ed25519)内容复制到 allowed_signers 文件中
# 如果是继续追加, 将 > 替换为 >>
cat ~/.ssh/id_ed25519.pub > ~/.config/git/allowed_signers
# 配置可信公钥
git config gpg.ssh.allowedSignersFile "~/.config/git/allowed_signers"
-

对 commit 签名

-
1
2
3
4
# 1. 当本地分支中的提交更改时,请将 -S 标志添加到 git commit 命令
git commit -S -m "YOUR_COMMIT_MESSAGE"
# 2. 在本地完成创建提交后,将其推送到 GitHub 上的远程仓库
git push
-

对 tag 签名

-
-

注意:如果 Git 客户端配置为默认对提交进行签名,GitHub Desktop 仅支持提交签名

-
-
1
2
3
4
# 1. 若要对标记进行签名,请将 -s 添加到 git tag 命令
git tag -s MYTAG
# 2. 通过运行 git tag -v [tag-name] 验证已签名的标记
git tag -v MYTAG
-

GPG

-
-

具体实践可参考官方文档 GPG 提交签名验证

-
-

S/MIME

-
-

S/MIME 签名验证需 Git 2.19 及以上版本

-
-

由于 S/MIME 用的比较少,这里就不做具体的演示,可参考 官方文档

-

验证签名

-
1
2
3
4
# 查看一下本地签名信息
# 正常情况,在 commit 提交号下
# good "git" signature for $(email) with $(publicKey)
git log --show-signature
-

问题

-

验证签名提示异常

-

No signature:

-
    -
  • 表示 Git 不知道要信任哪些 SSH 密钥
  • -
  • 解决方法:配置可信公钥列表
  • -
-

no-signature

No principal matched

-

no-principal

invalid key

-

invalid-key

-

如何给现有密钥更新密码

-

通过输入以下命令,您可以 更改 现有私钥 的密码而无需重新生成密钥对

-
1
2
3
4
5
6
7
# 修改 id_ed25519 密钥密码
$ ssh-keygen -p -f ~/.ssh/id_ed25519
> Enter old passphrase: [Type old passphrase]
> Key has comment 'your_email@example.com'
> Enter new passphrase (empty for no passphrase): [Type new passphrase]
> Enter same passphrase again: [Repeat the new passphrase]
> Your identification has been saved with the new passphrase.
-

参考

-
    -
  1. Git 提交使用 SSH 签名和 GPG 签名验证
  2. -
  3. SSH 提交签名验证
  4. -
  5. GitHub commit 签名指南
  6. -
  7. 维护代码的尊严:GPG签名让你的Git commit不再裸奔
  8. -
- -
- - - - - - -
-
- - - - - - -
-
-
- - - - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..80f46a893 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Jerry xu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 119c63701..6dbf4ef16 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,17 @@ BladeCode -![BladeCode](https://travis-ci.com/BladeCode/BladeCode.github.io.svg?branch=dev) - -## 附录 - -* [Conventional Commits](https://www.conventionalcommits.org) -* [优雅的提交你的 Git Commit Message](https://juejin.im/post/5afc5242f265da0b7f44bee4) -* [Commit message 和 Change log 编写指南](http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html) -* [gitemoji](https://gitmoji.carloscuesta.me) +![BladeCode](https://github.com/BladeCode/BladeCode.github.io/actions/workflows/action.yml/badge.svg?branch=dev) ## Thanks 1. [Node](https://nodejs.org) 2. [Hexo](https://hexo.io) -3. [Next](https://theme-next.js.org) -4. [LeanCloud](https://leancloud.cn) -5. [Utterances](https://utteranc.es) -6. [Chinese-copywriting-guidelines](https://github.com/sparanoid/chinese-copywriting-guidelines) +3. [Next](https://github.com/next-theme/hexo-theme-next) + +## Upgrade + +```node +// 需要手动选择升级的依赖包,按空格键选择,a 键切换所有,i 键反选选择 +yarn upgrade-interactive --latest +``` diff --git a/_config.next.yml b/_config.next.yml new file mode 100644 index 000000000..85001de55 --- /dev/null +++ b/_config.next.yml @@ -0,0 +1,937 @@ +# =============================================================== +# It's recommended to use Alternate Theme Config to configure NexT +# Modifying this file may result in merge conflict +# See: https://theme-next.js.org/docs/getting-started/configuration +# =============================================================== + +# --------------------------------------------------------------- +# Theme Core Configuration Settings +# See: https://theme-next.js.org/docs/theme-settings/ +# --------------------------------------------------------------- + +# Allow to cache content generation. +cache: + enable: true + +# Remove unnecessary files after hexo generate. +minify: true + +# Define custom file paths. +# Create your custom files in site directory `source/_data` and uncomment needed files below. +custom_file_path: + #head: source/_data/head.njk + #header: source/_data/header.njk + #sidebar: source/_data/sidebar.njk + #postMeta: source/_data/post-meta.njk + #postBodyStart: source/_data/post-body-start.njk + #postBodyEnd: source/_data/post-body-end.njk + #footer: source/_data/footer.njk + #bodyEnd: source/_data/body-end.njk + #variable: source/_data/variables.styl + #mixin: source/_data/mixins.styl + #style: source/_data/styles.styl + + +# --------------------------------------------------------------- +# Scheme Settings +# --------------------------------------------------------------- + +# Schemes +# scheme: Muse +scheme: Mist +#scheme: Pisces +#scheme: Gemini + +# Dark Mode +darkmode: true + + +# --------------------------------------------------------------- +# Site Information Settings +# --------------------------------------------------------------- + +favicon: + small: https://res.cloudinary.com/incoder/image/upload/v1525515979/favicon-16x16.png + medium: https://res.cloudinary.com/incoder/image/upload/v1525515979/favicon-32x32.png + apple_touch_icon: https://res.cloudinary.com/incoder/image/upload/v1525515979/apple-touch-icon.png + safari_pinned_tab: /images/logo.svg + #android_manifest: /manifest.json + +# Custom Logo (Warning: Do not support scheme Mist) +custom_logo: #/uploads/custom-logo.png + +# Creative Commons 4.0 International License. +# See: https://creativecommons.org/about/cclicenses/ +creative_commons: + # Available values: by | by-nc | by-nc-nd | by-nc-sa | by-nd | by-sa | cc-zero + license: by-nc-sa + # Available values: big | small + size: small + sidebar: true + post: true + # You can set a language value if you prefer a translated version of CC license, e.g. deed.zh + # CC licenses are available in 39 languages, you can find the specific and correct abbreviation you need on https://creativecommons.org + language: + +# Open graph settings +# See: https://hexo.io/docs/helpers#open-graph +open_graph: + enable: true + options: + #twitter_card: + #twitter_id: + #twitter_site: + #twitter_image: + #google_plus: + #fb_admins: + #fb_app_id: + + +# --------------------------------------------------------------- +# Menu Settings +# --------------------------------------------------------------- + +# Usage: `Key: /link/ || icon` +# Key is the name of menu item. If the translation for this item is available, the translated text will be loaded, otherwise the Key name will be used. Key is case-sensitive. +# Value before `||` delimiter is the target link, value after `||` delimiter is the name of Font Awesome icon. +# External url should start with http:// or https:// +menu: + home: / || fa fa-home + tags: /tags/ || fa fa-tags + categories: /categories/ || fa fa-th + archives: /archives/ || fa fa-archive + #schedule: /schedule/ || fa fa-calendar + #sitemap: /sitemap.xml || fa fa-sitemap + #commonweal: /404/ || fa fa-heartbeat + app: https://incoder.app || fas fa-seedling + # books: /books/ || fa fa-book + # movies: /movies || fa fa-film + # music: https://museflow.io || fa fa-music + # plan: https://www.notion.so/incoder/3d9638dbe2cb48a9a724a39bf9723717?v=e11c491037cd4fcda3a7b320bf996c5a || fa fa-calendar-check + # backend: https://backend.incoder.org || fa fa-coffee + # mobile: https://mobile.incoder.org || fa fa-feather + links: /links/ || fa fa-user-friends + about: /about/ || fa fa-user + +# Enable / Disable menu icons / item badges. +menu_settings: + icons: true + badges: true + + +# --------------------------------------------------------------- +# Sidebar Settings +# See: https://theme-next.js.org/docs/theme-settings/sidebar +# --------------------------------------------------------------- + +sidebar: + # Sidebar position. Available values: left | right + position: right + + # Sidebar width. + # Applicable to Muse | Mist and mobile of Pisces | Gemini. + width_expanded: 320 + # Applicable to desktop of Pisces | Gemini. + width_dual_column: 240 + + # Sidebar display. + # Applicable to Muse | Mist and mobile of Pisces | Gemini. + # Available values: + # - post expand on posts automatically. Default. + # - always expand for all pages automatically. + # - hide expand only when click on the sidebar toggle icon. + # - remove totally remove sidebar including sidebar toggle. + display: post + + # Sidebar padding in pixels. + padding: 18 + # Sidebar offset from top menubar in pixels (only for Pisces | Gemini). + offset: 12 + +# Sidebar Avatar +avatar: + # Replace the default image and set the url here. + url: https://res.cloudinary.com/incoder/image/upload/v1525515979/avatar.png #/images/avatar.gif + # If true, the avatar will be displayed in circle. + rounded: false + # If true, the avatar will be rotated with the cursor. + rotated: false + +# Posts / Categories / Tags in sidebar. +site_state: true + +# Social Links +# Usage: `Key: permalink || icon` +# Key is the link label showing to end users. +# Value before `||` delimiter is the target permalink, value after `||` delimiter is the name of Font Awesome icon. +social: + GitHub: https://github.com/BladeCode || fab fa-github + E-Mail: mailto:incoder.xu@gmail.com || fa fa-envelope + Weibo: https://weibo.com/onblade || fab fa-weibo + Twitter: https://twitter.com/_incoder || fa-brands fa-x-twitter + Medium: https://medium.com/@incoder || fab fa-medium + #FB Page: https://www.facebook.com/yourname || fab fa-facebook + #StackOverflow: https://stackoverflow.com/yourname || fab fa-stack-overflow + #YouTube: https://youtube.com/yourname || fab fa-youtube + #Instagram: https://instagram.com/yourname || fab fa-instagram + #Skype: skype:yourname?call|chat || fab fa-skype + +social_icons: + enable: true + icons_only: false + transition: true + +# Blog rolls +links_settings: + icon: fa fa-user-friends + # Available values: block | inline + layout: block + +links: + #Title: https://example.com + +# Table of Contents in the Sidebar +# Front-matter variable (nonsupport wrap expand_all). +toc: + enable: true + # Automatically add list number to toc. + number: true + # If true, all words will placed on next lines if header width longer then sidebar width. + wrap: false + # If true, all level of TOC in a post will be displayed, rather than the activated part of it. + expand_all: false + # Maximum heading depth of generated toc. + max_depth: 6 + + +# --------------------------------------------------------------- +# Footer Settings +# See: https://theme-next.js.org/docs/theme-settings/footer +# --------------------------------------------------------------- + +# Show multilingual switcher in footer. +language_switcher: false + +footer: + # Specify the year when the site was setup. If not defined, current year will be used. + since: 2018 + + # Icon between year and copyright info. + icon: + # Icon name in Font Awesome. See: https://fontawesome.com/icons + name: fa fa-heart + # If you want to animate the icon, set it to true. + animated: true + # Change the color of icon, using Hex Code. + color: "#FF66B3" + + # If not defined, `author` from Hexo `_config.yml` will be used. + # Set to `false` to disable the copyright statement. + copyright: + + # Powered by Hexo & NexT + powered: false + + # Beian ICP and gongan information for Chinese users. See: https://beian.miit.gov.cn, https://beian.mps.gov.cn + beian: + enable: false + icp: + # The digit in the num of gongan beian. + gongan_id: + # The full num of gongan beian. + gongan_num: + # The icon for gongan beian. Login and See: https://beian.mps.gov.cn/web/business/businessHome/website + gongan_icon_url: + + +# --------------------------------------------------------------- +# Post Settings +# See: https://theme-next.js.org/docs/theme-settings/posts +# --------------------------------------------------------------- + +# Use `description` in front-matter to specify post excerpt. +excerpt_description: true + +# Read more button +# If true, the read more button will be displayed in excerpt section. +read_more_btn: true + +# Post meta display settings +post_meta: + item_text: true + created_at: true + updated_at: + enable: false + another_day: false + categories: true + +# Post wordcount display settings +# Dependencies: https://github.com/next-theme/hexo-word-counter +symbols_count_time: + separated_meta: true + item_text_total: true + +# Use icon instead of the symbol # to indicate the tag at the bottom of the post +tag_icon: true + +# Donate (Sponsor) settings +# Front-matter variable (nonsupport animation). +reward_settings: + # If true, a donate button will be displayed in every article by default. + enable: true + animation: true + +reward: + wechatpay: https://res.cloudinary.com/incoder/image/upload/v1525515979/wechatpay.png + alipay: https://res.cloudinary.com/incoder/image/upload/v1525515979/alipay.png + #paypal: /images/paypal.png + #bitcoin: /images/bitcoin.png + +# Subscribe through Telegram Channel, Twitter, etc. +# Usage: `Key: permalink || icon` (Font Awesome) +follow_me: + #Twitter: https://twitter.com/username || fab fa-twitter + #Telegram: https://t.me/channel_name || fab fa-telegram + Slack: https://incoder.slack.com || fab fa-slack + WeChat: https://res.cloudinary.com/incoder/image/upload/v1554537454/qrcode_incoder.jpg || fab fa-weixin + RSS: /atom.xml || fa fa-rss + +# Related popular posts +# Dependencies: https://github.com/sergeyzwezdin/hexo-related-posts +related_posts: + enable: false + icon: fa fa-signs-post + +# Post edit +# Easily browse and edit blog source code online. +post_edit: + enable: false + url: https://github.com/user-name/repo-name/tree/branch-name/subdirectory-name/ # Link for view source + #url: https://github.com/user-name/repo-name/edit/branch-name/subdirectory-name/ # Link for fork & edit + +# Show previous post and next post in post footer if exists +# Available values: left | right | false +post_navigation: left + + +# --------------------------------------------------------------- +# Custom Page Settings +# See: https://theme-next.js.org/docs/theme-settings/custom-pages +# --------------------------------------------------------------- + +# TagCloud settings for tags page. +tagcloud: + min: 12 # Minimum font size in px + max: 30 # Maximum font size in px + amount: 200 # Total amount of tags + orderby: name # Order of tags + order: 1 # Sort order + +# Google Calendar +# Share your recent schedule to others via calendar page. +calendar: + calendar_id: # Your Google account E-Mail + api_key: + orderBy: startTime + showLocation: false + offsetMax: 72 # Time Range + offsetMin: 4 # Time Range + showDeleted: false + singleEvents: true + maxResults: 250 + + +# --------------------------------------------------------------- +# Misc Theme Settings +# See: https://theme-next.js.org/docs/theme-settings/miscellaneous +# --------------------------------------------------------------- + +# Preload styles and preconnect CDN for fonts and plugins. +# For more information: https://www.w3.org/TR/resource-hints/#preconnect +preconnect: true + +# Set the text alignment in posts / pages. +text_align: + # Available values: start | end | left | right | center | justify | justify-all | match-parent + desktop: justify + mobile: justify + +# Reduce padding / margin indents on devices with narrow width. +mobile_layout_economy: true + +# Browser header panel color. +theme_color: + light: "#222" + dark: "#222" + +# Override browsers' default behavior. +body_scrollbar: + # Place the scrollbar over the content. + overlay: false + # Present the scrollbar even if the content is not overflowing. + stable: false + +codeblock: + # Code Highlight theme + # All available themes: https://theme-next.js.org/highlight/ + theme: + light: base16/humanoid-light + dark: base16/humanoid-dark + prism: + light: prism + dark: prism-dark + # Add copy button on codeblock + copy_button: + enable: true + # Available values: default | flat | mac + style: mac + # Fold code block + fold: + enable: true + height: 300 + +back2top: + enable: true + # Back to top in sidebar. + sidebar: false + # Scroll percent label in b2t button. + scrollpercent: true + +# Reading progress bar +reading_progress: + enable: true + # Available values: left | right + start_at: left + # Available values: top | bottom + position: top + reversed: true + color: "#37c6c0" + height: 3px + +# Bookmark Support +bookmark: + enable: false + # Customize the color of the bookmark. + color: "#222" + # If auto, save the reading progress when closing the page or clicking the bookmark-icon. + # If manual, only save it by clicking the bookmark-icon. + save: auto + +# `Follow me on GitHub` banner in the top-right corner. +github_banner: + enable: false + permalink: https://github.com/yourname + + +# --------------------------------------------------------------- +# Font Settings +# --------------------------------------------------------------- +# Find fonts on Google Fonts (https://fonts.google.com) +# All fonts set here will have the following styles: +# light | light italic | normal | normal italic | bold | bold italic +# Be aware that setting too much fonts will cause site running slowly +# --------------------------------------------------------------- +# Web Safe fonts are recommended for `global` (and `title`): +# Arial | Tahoma | Helvetica | Times New Roman | Courier New | Verdana | Georgia | Palatino | Garamond | Comic Sans MS | Trebuchet MS +# --------------------------------------------------------------- + +font: + enable: false + + # Uri of fonts host, e.g. https://fonts.googleapis.com (Default). + host: + + # Font options: + # `external: true` will load this font family from `host` above. + # `family: Times New Roman`. Without any quotes. + # `size: x.x`. Use `em` as unit. Default: 1 (16px) + + # Global font settings used for all elements inside . + global: + external: true + family: Lato + size: + + # Font settings for site title (.site-title). + title: + external: true + family: + size: + + # Font settings for headlines (

to

). + headings: + external: true + family: + size: + + # Font settings for posts (.post-body). + posts: + external: true + family: + + # Font settings for and code blocks. + codes: + external: true + family: + + +# --------------------------------------------------------------- +# SEO Settings +# See: https://theme-next.js.org/docs/theme-settings/seo +# --------------------------------------------------------------- + +# If true, site-subtitle will be added to the title of index page. +# Remember to set up your site-subtitle in Hexo `_config.yml` (e.g. subtitle: Subtitle) +index_with_subtitle: true + +# Automatically add external URL with Base64 encrypt & decrypt. +exturl: true +# If true, an icon will be attached to each external URL +exturl_icon: true + +# Google Webmaster tools verification. +# See: https://developers.google.com/search +google_site_verification: + +# Bing Webmaster tools verification. +# See: https://www.bing.com/webmasters +bing_site_verification: 24D43F00CD6C02717E897A3C151C498D + +# Yandex Webmaster tools verification. +# See: https://webmaster.yandex.ru +yandex_site_verification: + +# Baidu Webmaster tools verification. +# See: https://ziyuan.baidu.com/site +baidu_site_verification: + + +# --------------------------------------------------------------- +# Tags Settings +# See: https://theme-next.js.org/docs/tag-plugins/ +# --------------------------------------------------------------- + +# Note tag (bootstrap callout) +note: + # Note tag style values: + # - simple bootstrap callout old alert style. Default. + # - modern bootstrap callout new (v2-v3) alert style. + # - flat flat callout style with background, like on Mozilla or StackOverflow. + # - disabled disable all CSS styles import of note tag. + style: simple + icons: true + # Offset lighter of background in % for modern and flat styles (modern: -12 | 12; flat: -18 | 6). + # Offset also applied to label tag variables. This option can work with disabled note tag. + light_bg_offset: 0 + +# Tabs tag +tabs: + # Make the nav bar of tabs with long content stick to the top. + sticky: true + transition: + tabs: true + labels: true + +# PDF tag +# NexT will try to load pdf files natively, if failed, pdf.js will be used. +# So, you have to install the dependency of pdf.js if you want to use pdf tag and make it available to all browsers. +# Dependencies: https://github.com/next-theme/theme-next-pdf +pdf: + enable: false + # Default height + height: 500px + +# Mermaid tag +mermaid: + enable: true + # Available themes: default | dark | forest | neutral + theme: + light: default + dark: dark + +# WaveDrom tag +wavedrom: + enable: true + +# --------------------------------------------------------------- +# Third Party Plugins & Services Settings +# See: https://theme-next.js.org/docs/third-party-services/ +# More plugins: https://github.com/next-theme/awesome-next +# --------------------------------------------------------------- + +# --------------------------------------------------------------- +# Math Formulas Render Support +# See: https://theme-next.js.org/docs/third-party-services/math-equations +# Warning: Please install / uninstall the relevant renderer according to the documentation. +# Server-side plugin: https://github.com/next-theme/hexo-filter-mathjax +# --------------------------------------------------------------- + +math: + # Default (false) will load mathjax / katex script on demand. + # That is it only render those page which has `mathjax: true` in front-matter. + # If you set it to true, it will load mathjax / katex script EVERY PAGE. + every_page: false + + mathjax: + enable: false + # Available values: none | ams | all + tags: none + + katex: + enable: false + # See: https://github.com/KaTeX/KaTeX/tree/master/contrib/copy-tex + copy_tex: false + + +# --------------------------------------------------------------- +# External Libraries +# See: https://theme-next.js.org/docs/third-party-services/external-libraries +# --------------------------------------------------------------- + +# Easily enable fast Ajax navigation on your website. +# For more information: https://github.com/next-theme/pjax +pjax: false + +# FancyBox is a tool that offers a nice and elegant way to add zooming functionality for images. +# For more information: https://fancyapps.com/fancybox/ +fancybox: true + +# Medium Zoom is a JavaScript library for zooming images like Medium. +# Warning: Do not enable both `fancybox` and `mediumzoom`. +# For more information: https://medium-zoom.francoischalifour.com +mediumzoom: true + +# Vanilla JavaScript plugin for lazyloading images. +# For more information: https://apoorv.pro/lozad.js/demo/ +lazyload: false + +# Automatically insert whitespace between CJK and half-width characters. +# For more information: https://github.com/vinta/pangu.js +# Server-side plugin: https://github.com/next-theme/hexo-pangu +pangu: true + +# Prefetch links based on what is in the user's viewport. +# For more information: https://getquick.link +# Front-matter variable (nonsupport home archive). +quicklink: + enable: false + + # Home page and archive page can be controlled through home and archive options below. + # This configuration item is independent of `enable`. + home: false + archive: false + + # Default (true) will initialize quicklink after the load event fires. + delay: true + # Custom a time in milliseconds by which the browser must execute prefetching. + timeout: 3000 + # Default (true) will attempt to use the fetch() API if supported (rather than link[rel=prefetch]). + priority: true + + +# --------------------------------------------------------------- +# Animation Settings +# --------------------------------------------------------------- + +# Use Animate.css to animate everything. +# For more information: https://animate.style +motion: + enable: true + async: false + transition: + # All available transition variants: https://theme-next.js.org/animate/ + menu_item: fadeInDown + post_block: fadeIn + post_header: fadeInDown + post_body: fadeInDown + coll_header: fadeInLeft + # Only for Pisces | Gemini. + sidebar: fadeInUp + +# Progress bar in the top during page loading. +# For more information: https://github.com/CodeByZach/pace +pace: + enable: true + # All available colors: + # black | blue | green | orange | pink | purple | red | silver | white | yellow + color: blue + # All available themes: + # big-counter | bounce | barber-shop | center-atom | center-circle | center-radar | center-simple + # corner-indicator | fill-left | flat-top | flash | loading-bar | mac-osx | material | minimal + theme: minimal + +# Generate a ribbon in your website with HTML5 canvas. +# For more information: https://github.com/hustcc/ribbon.js +canvas_ribbon: + enable: false + size: 300 # The width of the ribbon + alpha: 0.6 # The transparency of the ribbon + zIndex: -1 # The display level of the ribbon + + +# --------------------------------------------------------------- +# Comments Settings +# See: https://theme-next.js.org/docs/third-party-services/comments +# --------------------------------------------------------------- + +# Multiple Comment System Support +comments: + # Available values: tabs | buttons + style: tabs + # Choose a comment system to be displayed by default. + # Available values: disqus | disqusjs | changyan | livere | gitalk | utterances + active: + # Setting `true` means remembering the comment system selected by the visitor. + storage: true + # Lazyload all comment systems. + lazyload: false + # Modify texts or order for any naves, here are some examples. + nav: + #disqus: + # text: Load Disqus + # order: -1 + #gitalk: + # order: -2 + +# Disqus +# For more information: https://disqus.com +disqus: + enable: false + shortname: + count: true + +# DisqusJS +# For more information: https://disqusjs.skk.moe +disqusjs: + enable: false + # API Endpoint of Disqus API (https://disqus.com/api/docs/). + # Leave api empty if you are able to connect to Disqus API. Otherwise you need a reverse proxy for it. + # For example: + # api: https://disqus.skk.moe/disqus/ + api: + apikey: # Register new application from https://disqus.com/api/applications/ + shortname: # See: https://disqus.com/admin/settings/general/ + +# Changyan +# For more information: https://changyan.kuaizhan.com +changyan: + enable: false + appid: + appkey: + # Show comments count + count: true + +# LiveRe comments system +# You can get your uid from https://livere.com/insight/myCode (General web site) +livere_uid: # + +# Gitalk +# For more information: https://gitalk.github.io +gitalk: + enable: false + github_id: # GitHub repo owner + repo: # Repository name to store issues + client_id: # GitHub Application Client ID + client_secret: # GitHub Application Client Secret + admin_user: # GitHub repo owner and collaborators, only these guys can initialize gitHub issues + distraction_free_mode: true # Facebook-like distraction free mode + # When the official proxy is not available, you can change it to your own proxy address + proxy: https://cors-anywhere.azm.workers.dev/https://github.com/login/oauth/access_token # This is official proxy address + # Gitalk's display language depends on user's browser or system environment + # If you want everyone visiting your site to see a uniform language, you can set a force language value + # Available values: en | es-ES | fr | ru | zh-CN | zh-TW + language: + +# Utterances +# For more information: https://utteranc.es +utterances: + enable: true + repo: BladeCode/BladeCode.github.io # Github repository owner and name + # Available values: pathname | url | title | og:title + issue_term: title + # Available values: github-light | github-dark | preferred-color-scheme | github-dark-orange | icy-dark | dark-blue | photon-dark | boxy-light + theme: github-light + +# Isso +# For more information: https://isso-comments.de +isso: # + + +# --------------------------------------------------------------- +# Post Widgets & Content Sharing Services +# See: https://theme-next.js.org/docs/third-party-services/post-widgets +# --------------------------------------------------------------- + +# AddToAny Share. See: https://www.addtoany.com +addtoany: + enable: false + buttons: + - facebook + - twitter + + +# --------------------------------------------------------------- +# Statistics and Analytics +# See: https://theme-next.js.org/docs/third-party-services/statistics-and-analytics +# --------------------------------------------------------------- + +# Google Analytics +# See: https://analytics.google.com +google_analytics: + tracking_id: UA-100235665-1 # + # By default, NexT will load an external gtag.js script on your site. + # If you only need the pageview feature, set the following option to true to get a better performance. + only_pageview: false + # only needed if you are using `only_pageview` mode, https://developers.google.com/analytics/devguides/collection/protocol/ga4 + measure_protocol_api_secret: + +# Baidu Analytics +# See: https://tongji.baidu.com +baidu_analytics: # + +# Growingio Analytics +# See: https://www.growingio.com +growingio_analytics: # + +# Cloudflare Web Analytics +# See: https://www.cloudflare.com/web-analytics/ +cloudflare_analytics: + +# Microsoft Clarity Analytics +# See: https://clarity.microsoft.com/ +clarity_analytics: # + +# Matomo Analytics +# See: https://matomo.org/ +matomo: + enable: false + server_url: # https://www.example.com/ + site_id: # + +# Umami Analytics +# See: https://umami.is/ +umami: + enable: false + script_url: # https://umami.example.com/script.js + website_id: # + host_url: # + +# Plausible Analytics +# See: https://plausible.io/ +plausible: + enable: false + script_url: # https://plausible.io/js/script.js + site_domain: # www.example.com + +# Show number of visitors of each article. +# You can visit https://www.leancloud.cn to get AppID and AppKey. +leancloud_visitors: + enable: true + app_id: 3ShrIGfQL4TLamd48UtbdDEK-gzGzoHsz # + app_key: cW8VJrB2yJiIBLtYA0KdE8vW # + # Required for apps from CN region + server_url: # + # Dependencies: https://github.com/theme-next/hexo-leancloud-counter-security + # If you don't care about security in leancloud counter and just want to use it directly + # (without hexo-leancloud-counter-security plugin), set `security` to `false`. + security: false + +# Another tool to show number of visitors to each article. +# Visit https://console.firebase.google.com/u/0/ to get apiKey and projectId. +# Visit https://firebase.google.com/docs/firestore/ to get more information about firestore. +firestore: + enable: false + collection: articles # Required, a string collection name to access firestore database + apiKey: # Required + projectId: # Required + +# Show Views / Visitors of the website / page with busuanzi. +# For more information: http://ibruce.info/2015/04/04/busuanzi/ +busuanzi_count: + enable: false + total_visitors: true + total_visitors_icon: fa fa-user + total_views: true + total_views_icon: fa fa-eye + post_views: true + post_views_icon: far fa-eye + + +# --------------------------------------------------------------- +# Search Services +# See: https://theme-next.js.org/docs/third-party-services/search-services +# --------------------------------------------------------------- + +# Algolia Search +# For more information: https://www.algolia.com +algolia_search: + enable: false + hits: + per_page: 10 + +# Local Search +# Dependencies: https://github.com/next-theme/hexo-generator-searchdb +local_search: + enable: true + # Show top n results per article, show all results by setting to -1 + top_n_per_article: 1 + # Unescape html strings to the readable one. + unescape: true + # Preload the search data when the page loads. + preload: true + + +# --------------------------------------------------------------- +# Chat Services +# See: https://theme-next.js.org/docs/third-party-services/chat-services +# --------------------------------------------------------------- + +# A button to open designated chat widget in sidebar. +# Firstly, you need to enable and configure the chat service. +chat: + enable: false + icon: fa fa-comment # Icon name in Font Awesome, set to `false` to disable icon. + +# Chatra is a functional, easy to use piece of chat software for websites. +# For more information: https://chatra.com +# Dashboard: https://app.chatra.io/settings/general +chatra: + enable: false + async: true + id: # Visit Dashboard to get your ChatraID + #embed: # Unfinished experimental feature for developers. See: https://chatra.com/help/api/#injectto + +# Tidio is a powerful, all-in-one customer service tool. +# For more information: https://www.tidio.com +# Dashboard: https://www.tidio.com/panel/dashboard +tidio: + enable: false + key: # Public Key, get it from dashboard. See: https://www.tidio.com/panel/settings/developer + + +# --------------------------------------------------------------- +# CDN Settings +# See: https://theme-next.js.org/docs/advanced-settings/vendors +# --------------------------------------------------------------- + +vendors: + # The CDN provider of NexT internal scripts. + # Available values: local | jsdelivr | unpkg | cdnjs | custom + # Warning: If you are using the latest master branch of NexT, please set `internal: local` + internal: local + # The default CDN provider of third-party plugins. + # Available values: local | jsdelivr | unpkg | cdnjs | custom + # Dependencies for `plugins: local`: https://github.com/next-theme/plugins + plugins: cdnjs + # Custom CDN URL + # For example: + # custom_cdn_url: https://cdn.jsdelivr.net/npm/${npm_name}@${version}/${minified} + # custom_cdn_url: https://cdnjs.cloudflare.com/ajax/libs/${cdnjs_name}/${version}/${cdnjs_file} + custom_cdn_url: + +# Assets +# Accelerate delivery of static files using a CDN +# The js option is only valid when vendors.internal is local. +css: css +js: js +images: images \ No newline at end of file diff --git a/_config.yml b/_config.yml new file mode 100644 index 000000000..aa91c8ed3 --- /dev/null +++ b/_config.yml @@ -0,0 +1,210 @@ +# Hexo Configuration +## Docs: https://hexo.io/docs/configuration.html +## Source: https://github.com/hexojs/hexo/ + +# Site +# +title: 星海 +subtitle: Life's A Struggle! +description: '' +keywords: incoder BladeCode +author: Jerry Xu +language: zh-CN +timezone: '' + +# URL +## If your site is put in a subdirectory, set url as 'http://example.com/child' and root as '/child/' +url: https://incoder.org +root: / +permalink: :year/:month/:day/:title/ +permalink_defaults: +pretty_urls: + trailing_index: true # Set to false to remove trailing 'index.html' from permalinks + trailing_html: true # Set to false to remove trailing '.html' from permalinks + +# Directory +source_dir: source +public_dir: public +tag_dir: tags +archive_dir: archives +category_dir: categories +code_dir: downloads/code +i18n_dir: :lang +skip_render: README.md + +# Writing +new_post_name: :title.md # File name of new posts +default_layout: post +titlecase: false # Transform title into titlecase +external_link: + enable: true # Open external links in new tab + field: site # Apply to the whole site + exclude: '' +filename_case: 0 +render_drafts: false +post_asset_folder: false +relative_link: false +future: true +highlight: + enable: true + line_number: true + auto_detect: false + tab_replace: '' + wrap: true + hljs: false +prismjs: + enable: false + preprocess: true + line_number: true + tab_replace: '' + +# Home page setting +# path: Root path for your blogs index page. (default = '') +# per_page: Posts displayed per page. (0 = disable pagination) +# order_by: Posts order. (Order by date descending by default) +index_generator: + path: '' + per_page: 10 + order_by: -date + +# Category & Tag +default_category: uncategorized +category_map: +tag_map: + +# Metadata elements +## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta +meta_generator: true + +# Date / Time format +## Hexo uses Moment.js to parse and display date +## You can customize the date format as defined in +## http://momentjs.com/docs/#/displaying/format/ +date_format: YYYY-MM-DD +time_format: HH:mm:ss +## updated_option supports 'mtime', 'date', 'empty' +updated_option: 'mtime' + +# Pagination +## Set per_page to 0 to disable pagination +per_page: 10 +pagination_dir: page + +# Include / Exclude file(s) +## include:/exclude: options only apply to the 'source/' folder +include: +exclude: +ignore: + +# Extensions +## Plugins: https://hexo.io/plugins/ +## Themes: https://hexo.io/themes/ +theme: next + +# Deployment +## Docs: https://hexo.io/docs/one-command-deployment +deploy: +- type: git + repo: https://github.com/BladeCode/BladeCode.github.io.git + branch: master + +# Extensions +## Plugins: https://hexo.io/plugins/ + +# RSS订阅支持 +plugin: +- hexo-generator-feed +# Feed Atom +feed: + type: atom + path: atom.xml + limit: 20 + +# hexo-symbols-count-time +symbols_count_time: + symbols: true + time: true + total_symbols: true + total_time: true + +# search +search: + path: search.xml + field: post + content: true + format: html + limit: 10000 + +# https://github.com/mythsman/hexo-douban +# douban: +# id: 120640962 +# # user: incoder +# builtin: true +# item_per_page: 10 +# meta_max_line: 4 +# customize_layout: page +# book: +# title: '书中自有黄金屋' +# quote: '只有学习才能让我得到真正的快乐~' +# movie: +# title: '有人的地方,就有江湖!' +# quote: '时光追忆!' +# game: +# title: '那时候太年轻' +# quote: '游戏可以重来,人生呢?' +# timeout: 10000 # optional + +leancloud_counter_security: + enable_sync: true + app_id: 3ShrIGfQL4TLamd48UtbdDEK-gzGzoHsz + app_key: cW8VJrB2yJiIBLtYA0KdE8vW + username: Blade # Will be asked while deploying if is left blank + password: bladecode # Recommmended to be left blank. Will be asked while deploying if is left blank + +# Markdown-it config +## Docs: https://github.com/celsomiranda/hexo-renderer-markdown-it/wiki +markdown: + # 渲染设置 + render: + # 置为true时,html内容保持不变;置为false时,html内容将被转义成普通字符串 + html: true + # 是否生成与XHTML完全兼容的标签(虽然我不懂是什么意思) + xhtmlOut: false + # 置为true时,每个换行符都被渲染成一个
(即Hexo的默认表现);置为false时,只有空行才会被渲染为
(GFM的默认表现) + breaks: true + # 是否自动识别链接并把它渲染成链接 + linkify: true + # 是否自动识别印刷格式(意思是把(c)渲染为©这样的) + typographer: true + # 如果typographer被设置为true,则该选项用于设置将dumb quotes("")自动替换为smart quotes + quotes: '“”‘’' + # 设置所需插件 + plugins: + - markdown-it-abbr + - markdown-it-footnote + - markdown-it-ins + - markdown-it-sub + - markdown-it-sup + # 锚点设置(因为我没有尝试相关内容,所以就不翻译相关说明了) + anchors: + level: 2 + collisionSuffix: 'v' + permalink: true + permalinkClass: header-anchor + permalinkSymbol: '' + +# 文章密码访问 +encrypt: + enable: true + +# hexo-hide-posts +hide_posts: + # 可以改成其他你喜欢的名字 + filter: hidden + # 指定你想要传递隐藏文章的位置,比如让所有隐藏文章在存档页面可见 + # 常见的位置有:index, tag, category, archive, sitemap, feed, etc. + # 留空则默认全部隐藏 + public_generators: [] + # 为隐藏的文章添加 noindex meta 标签,阻止搜索引擎收录 + noindex: true + \ No newline at end of file diff --git a/about/index.html b/about/index.html deleted file mode 100644 index 1a6f7dc6c..000000000 --- a/about/index.html +++ /dev/null @@ -1,392 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -关于 | 星海 - - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- -

关于 -

- - - -
- - - - -
-
-

Life’s A Struggle!
-生命不息,折腾不止,当然这不能折腾

- -
-

也不想声讨,宋岳庭原唱单曲《Life’s A Struggle》在国内,遭到全网下架!!!
-🥰 网易云于 2024.04.05 重新上架 🥳
-😭 但由于版权保护,无法外链播放 😂

-
- -

关于我

-

坐标杭州,理工男,机械制造与自动化专业
-14 年毕业后,误打误撞闯进计算机的世界
-16 年开始自学 Android 开发,后自学 Java 服务端相关开发,现从事服务端开发工作

-

关于本站

-

本站基于 HexoNexT 搭建,使用 Git-Pages 托管服务

-
-

该网站从 2018 年开始了我的记录之旅,主要用来 记录学习 分享生活,和一些归档资源的整理

-
    -
  • 资源:便捷,高效,有趣的工具资源
  • -
  • 经验:工作中一些问题解决方式及思考
  • -
  • 阅读:阅读一些书籍的收获以及感兴趣的文字
  • -
  • 学习:编程语言,热门开源框架的学习过程记录
  • -
  • 影音:视觉,听觉的享受分享
  • -
-

不止一面

-

优雅永不过时,相信文字的力量

-
- -

重要变更

-

2024-06-24

-
    -
  1. 博客正式更名为《星海》,因为我的征程就是星辰大海
  2. -
  3. 博客变更主题 NexT Scheme 为 Mist
  4. -
- -
- - - -
- - - - - -
-
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/03/index.html b/archives/2018/03/index.html deleted file mode 100644 index 66ada7118..000000000 --- a/archives/2018/03/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/04/index.html b/archives/2018/04/index.html deleted file mode 100644 index 79a0d3d11..000000000 --- a/archives/2018/04/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/05/index.html b/archives/2018/05/index.html deleted file mode 100644 index 7fc8bcda1..000000000 --- a/archives/2018/05/index.html +++ /dev/null @@ -1,440 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/06/index.html b/archives/2018/06/index.html deleted file mode 100644 index 8964089d9..000000000 --- a/archives/2018/06/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/07/index.html b/archives/2018/07/index.html deleted file mode 100644 index d6993e720..000000000 --- a/archives/2018/07/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/08/index.html b/archives/2018/08/index.html deleted file mode 100644 index 330ef13e3..000000000 --- a/archives/2018/08/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/10/index.html b/archives/2018/10/index.html deleted file mode 100644 index 18de6b00d..000000000 --- a/archives/2018/10/index.html +++ /dev/null @@ -1,460 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/11/index.html b/archives/2018/11/index.html deleted file mode 100644 index 7097c38a0..000000000 --- a/archives/2018/11/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/12/index.html b/archives/2018/12/index.html deleted file mode 100644 index ab4e29378..000000000 --- a/archives/2018/12/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/index.html b/archives/2018/index.html deleted file mode 100644 index 763d8b91a..000000000 --- a/archives/2018/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/page/2/index.html b/archives/2018/page/2/index.html deleted file mode 100644 index 33ae4ceae..000000000 --- a/archives/2018/page/2/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/page/3/index.html b/archives/2018/page/3/index.html deleted file mode 100644 index 4a725f24c..000000000 --- a/archives/2018/page/3/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/page/4/index.html b/archives/2018/page/4/index.html deleted file mode 100644 index 80c72ef16..000000000 --- a/archives/2018/page/4/index.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/01/index.html b/archives/2019/01/index.html deleted file mode 100644 index c715eb7a0..000000000 --- a/archives/2019/01/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/05/index.html b/archives/2019/05/index.html deleted file mode 100644 index c24175a65..000000000 --- a/archives/2019/05/index.html +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/06/index.html b/archives/2019/06/index.html deleted file mode 100644 index 043f9e7fd..000000000 --- a/archives/2019/06/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/07/index.html b/archives/2019/07/index.html deleted file mode 100644 index 08201f031..000000000 --- a/archives/2019/07/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/08/index.html b/archives/2019/08/index.html deleted file mode 100644 index 40a514932..000000000 --- a/archives/2019/08/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/09/index.html b/archives/2019/09/index.html deleted file mode 100644 index 5854c74d8..000000000 --- a/archives/2019/09/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/10/index.html b/archives/2019/10/index.html deleted file mode 100644 index a5f85bb3b..000000000 --- a/archives/2019/10/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/11/index.html b/archives/2019/11/index.html deleted file mode 100644 index 1aab6388c..000000000 --- a/archives/2019/11/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/12/index.html b/archives/2019/12/index.html deleted file mode 100644 index c0fdb7761..000000000 --- a/archives/2019/12/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/index.html b/archives/2019/index.html deleted file mode 100644 index 37eeae5d0..000000000 --- a/archives/2019/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/page/2/index.html b/archives/2019/page/2/index.html deleted file mode 100644 index a974caae5..000000000 --- a/archives/2019/page/2/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2019/page/3/index.html b/archives/2019/page/3/index.html deleted file mode 100644 index 93ac504e7..000000000 --- a/archives/2019/page/3/index.html +++ /dev/null @@ -1,463 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/01/index.html b/archives/2020/01/index.html deleted file mode 100644 index bd295d29e..000000000 --- a/archives/2020/01/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/02/index.html b/archives/2020/02/index.html deleted file mode 100644 index 2bb087d9d..000000000 --- a/archives/2020/02/index.html +++ /dev/null @@ -1,440 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/03/index.html b/archives/2020/03/index.html deleted file mode 100644 index 77cef4eb9..000000000 --- a/archives/2020/03/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/04/index.html b/archives/2020/04/index.html deleted file mode 100644 index e40ff1771..000000000 --- a/archives/2020/04/index.html +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/05/index.html b/archives/2020/05/index.html deleted file mode 100644 index 219fb3dfd..000000000 --- a/archives/2020/05/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/06/index.html b/archives/2020/06/index.html deleted file mode 100644 index b9af8e411..000000000 --- a/archives/2020/06/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/07/index.html b/archives/2020/07/index.html deleted file mode 100644 index 5ad5f9aca..000000000 --- a/archives/2020/07/index.html +++ /dev/null @@ -1,440 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/08/index.html b/archives/2020/08/index.html deleted file mode 100644 index 0fcc52b52..000000000 --- a/archives/2020/08/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/09/index.html b/archives/2020/09/index.html deleted file mode 100644 index e113bfadd..000000000 --- a/archives/2020/09/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/10/index.html b/archives/2020/10/index.html deleted file mode 100644 index 589f7bf2d..000000000 --- a/archives/2020/10/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/11/index.html b/archives/2020/11/index.html deleted file mode 100644 index 831f75b5f..000000000 --- a/archives/2020/11/index.html +++ /dev/null @@ -1,500 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/12/index.html b/archives/2020/12/index.html deleted file mode 100644 index 9b7803194..000000000 --- a/archives/2020/12/index.html +++ /dev/null @@ -1,460 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/index.html b/archives/2020/index.html deleted file mode 100644 index c72981eb4..000000000 --- a/archives/2020/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/page/2/index.html b/archives/2020/page/2/index.html deleted file mode 100644 index 1eb265dec..000000000 --- a/archives/2020/page/2/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/page/3/index.html b/archives/2020/page/3/index.html deleted file mode 100644 index 117405489..000000000 --- a/archives/2020/page/3/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2020/page/4/index.html b/archives/2020/page/4/index.html deleted file mode 100644 index 255928634..000000000 --- a/archives/2020/page/4/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2021/02/index.html b/archives/2021/02/index.html deleted file mode 100644 index 153161682..000000000 --- a/archives/2021/02/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 20215 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2021/03/index.html b/archives/2021/03/index.html deleted file mode 100644 index 7531995d2..000000000 --- a/archives/2021/03/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 20215 -
- - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2021/05/index.html b/archives/2021/05/index.html deleted file mode 100644 index 6c431699e..000000000 --- a/archives/2021/05/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 20215 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2021/index.html b/archives/2021/index.html deleted file mode 100644 index 51d250fca..000000000 --- a/archives/2021/index.html +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 20215 -
- - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2022/06/index.html b/archives/2022/06/index.html deleted file mode 100644 index c47ae8d1a..000000000 --- a/archives/2022/06/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 20221 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2022/index.html b/archives/2022/index.html deleted file mode 100644 index 0a8778a31..000000000 --- a/archives/2022/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 20221 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2024/06/index.html b/archives/2024/06/index.html deleted file mode 100644 index 9b2779b9a..000000000 --- a/archives/2024/06/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 20241 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2024/index.html b/archives/2024/index.html deleted file mode 100644 index 055125a76..000000000 --- a/archives/2024/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 20241 -
- - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/index.html b/archives/index.html deleted file mode 100644 index bd79f14dc..000000000 --- a/archives/index.html +++ /dev/null @@ -1,532 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 20241 -
- - -
- 20221 -
- - -
- 20215 -
- - - - - - - - - - -
- 202040 -
- - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/10/index.html b/archives/page/10/index.html deleted file mode 100644 index 88ea4e010..000000000 --- a/archives/page/10/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/11/index.html b/archives/page/11/index.html deleted file mode 100644 index 073ef929b..000000000 --- a/archives/page/11/index.html +++ /dev/null @@ -1,463 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/2/index.html b/archives/page/2/index.html deleted file mode 100644 index 42661b15d..000000000 --- a/archives/page/2/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/3/index.html b/archives/page/3/index.html deleted file mode 100644 index d4acaa305..000000000 --- a/archives/page/3/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/4/index.html b/archives/page/4/index.html deleted file mode 100644 index 662c6f2c8..000000000 --- a/archives/page/4/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/5/index.html b/archives/page/5/index.html deleted file mode 100644 index b17ef0484..000000000 --- a/archives/page/5/index.html +++ /dev/null @@ -1,526 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 202040 -
- - - - - - - - - - - - - - -
- 201927 -
- - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/6/index.html b/archives/page/6/index.html deleted file mode 100644 index c9b8f7f25..000000000 --- a/archives/page/6/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/7/index.html b/archives/page/7/index.html deleted file mode 100644 index c515410ec..000000000 --- a/archives/page/7/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/8/index.html b/archives/page/8/index.html deleted file mode 100644 index c293a38bd..000000000 --- a/archives/page/8/index.html +++ /dev/null @@ -1,526 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201927 -
- - - - - - - - -
- 201833 -
- - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/page/9/index.html b/archives/page/9/index.html deleted file mode 100644 index b88965cd0..000000000 --- a/archives/page/9/index.html +++ /dev/null @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -归档 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
- - 很好! 目前共计 107 篇日志。 继续努力。 -
- - -
- 201833 -
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/atom.xml b/atom.xml deleted file mode 100644 index bf1ea310d..000000000 --- a/atom.xml +++ /dev/null @@ -1,454 +0,0 @@ - - - 星海 - - Life's A Struggle! - - - - 2024-08-11T12:14:45.511Z - https://incoder.org/ - - - Jerry Xu - - - - Hexo - - - Git 签名 - - https://incoder.org/2024/06/17/git-signature/ - 2024-06-17T22:30:50.000Z - 2024-08-11T12:14:45.511Z - - verified-commit

通常 Push 代码到远程托管平台(GitHub,Gitlab,Gitee 等),需要提前在托管平台上传我们 Git 账户的公钥(*.pub),平台使用上传的公钥来验证身份(本地的 Git 私钥与平台上的公钥配对,以确保你有权限读写该仓库),该验证只会在 Push 时进行检查

为什么要签名

虽然对 Push 做了检查,但依然不够安全,因为任何拥有该仓库权限的人,都可以在 commit/tag 时使用 git config user.name "假的用户名", git config user.email "假的邮箱地址" 命令来伪造提交者用户信息,这样我们根本无法追溯提交者的身份,所以我们需要给 commit/tag 签名,签名的目的是确保提交的代码在传输和存储过程中没有被篡改,并验证提交者的身份,同时也保证代码的可追溯性

commit 签名是在本地,在使用 git commit 命令时进行签名,push 时会将你的签名信息,原封不动 push 到远程仓库

commit 签名只是用于验证这条 commit 来自于你本人,与是否有权限操作远程仓库无关

如何签名

可以使用 GPG、SSH 或 S/MIME,可以在本地对 commit/tag 进行签名。 这些 commit/tag 在 GitHub 上标示为已验证,便于其他人信任更改来自可信的来源

SSH、GPG、S/MIME 区别

  • SSH 签名是最容易生成的,甚至可以将现有身份验证密钥上传到 GitHub 以用作签名密钥
  • 生成 GPG 签名比生成 SSH 密钥复杂,但 GPG 具有 SSH 没有的功能,GPG 密钥可以在不使用时过期或撤销
  • 较大型组织的环境中通常需要 S/MIME 签名

👍 SSH(常用)

SSH 签名验证需 Git 2.3.4 及以上版本

检查现有 SSH 密钥

1
2
3
# 查看本地 ~/.ssh 路径现有密钥
ls -al ~/.ssh
# 检查输出的列表

检查已有密钥列表已有 RSA 密钥,如果你已经有 SHA-2 算法生成 RSA 密钥 那么你可以 跳过 生产新 SSH 密钥,如果没有,为了安全,还是建议重新生成 SHA-2 算法生成 RSA 密钥

生产新 SSH 密钥

  1. 2022.03.15
    • 在该日期 GitHub 删除旧的、不安全的密钥类型来提高安全性
    • 自该日期起,不再支持 DSA 密钥 (ssh-dss)。 无法github.com 上向个人帐户添加新的 DSA 密钥
  2. 2021.11.02
    • 在该日期 之前 带有 valid_after 的 RSA 密钥 (ssh-rsa) 可以继续使用任何签名算法
    • 在该日期 之后 生成的 RSA 密钥 必须 使用 SHA-2 签名算法。 一些较旧的客户端可能需要升级才能使用 SHA-2 签名

在生成 SSH 密钥时,我们可以给 SSH 密钥添加密码,也可以不添加密码,这里根据需要选择是否添加密码

git-ssh-keygen

  1. 执行 ssh-keygen -t ed25519 -C "Jerry.x@outlook.com" 命令,这里的邮箱换成你的 GitHub 邮箱
  2. 确认密钥存放位置
    • 如果不需调整(默认:/User/blade/.ssh/id_ed25519),可 Enter 键进入下一步
    • 这里我重命名了密钥 /User/blade/.ssh/id_ed25519_test
  3. 给密钥设置密码
    • 如果无需设置,可 Enter 键进入下一步
    • 如果需设置,输入密码即可
  4. 确认输入的密钥密码,和上一步输入内容一致
  5. 提示生成的密钥存放位置和指纹信息

如果密钥 设置了密码,需要将 SSH 密钥添加到 ssh-agent

在向 ssh-agent 添加新的 SSH 密钥管理密钥前,应该检查现有 SSH 密钥并生成新的 SSH 密钥

如果已安装 GitHub Desktop,可使用它克隆存储库,而无需处理 SSH 密钥

  1. 在新的“管理员提升”__ PowerShell 窗口中,确保 ssh-agent 正在运行。 可以使用“使用 SSH 密钥密码”中的“自动启动 ssh agent”说明,或者手动启动它

    1
    2
    3
    # start the ssh-agent in the background
    Get-Service -Name ssh-agent | Set-Service -StartupType Manual
    Start-Service ssh-agent
  2. 在无提升权限的终端窗口中,将 SSH 私钥添加到 ssh-agent。 如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称

    1
    ssh-add c:/Users/YOU/.ssh/id_ed25519

将 SSH 密钥添加到该代理时,应使用默认的 macOS ssh-add 命令,而不是使用通过 macports、homebrew 或某些其他外部来源安装的应用程序

  1. 在后台启动 ssh 代理

    1
    2
    $ eval "$(ssh-agent -s)"
    > Agent pid 59566

    根据您的环境,您可能需要使用不同的命令。 例如,在启动 ssh-agent 之前,你可能需要通过运行 sudo -s -H 根访问,或者可能需要使用 exec ssh-agent bashexec ssh-agent zsh 运行 ssh-agent

  2. 如果你使用的是 macOS Sierra 10.12.2 或更高版本,则需要修改 ~/.ssh/config 文件以自动将密钥加载到 ssh-agent 中并在密钥链中存储密码

    • 检查你的 ~/.ssh/config 文件是否在默认位置

    • 如果文件不存在,请创建该文件

    • 打开你的 ~/.ssh/config 文件,然后修改文件以包含以下行。 如果您的 SSH 密钥文件与示例代码具有不同的名称或路径,请修改文件名或路径以匹配您当前的设置

      1
      2
      3
      4
      5
      6
      7
      8
      Host github.com
      # 如果看到了 Bad configuration option: usekeychain 错误,
      # 取消下一行注释,使用 IgnoreUnknown 配置
      # IgnoreUnknown UseKeychain
      AddKeysToAgent yes
      # 如果你选择不向密钥添加密码,应该省略 UseKeychain 行
      UseKeychain yes
      IdentityFile ~/.ssh/id_ed25519
  3. 将 SSH 私钥添加到 ssh-agent 并将密码存储在密钥链中。 如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称

    1
    ssh-add --apple-use-keychain ~/.ssh/id_ed25519

注意

  1. 当你将 SSH 密钥添加到 ssh-agent 时,--apple-use-keychain 选项会将密码存储在你的密钥链中。 如果选择不向密钥添加密码,请运行命令,而不使用 --apple-use-keychain 选项
  2. 选项 --apple-use-keychain 位于 Apple 的 ssh-add 标准版本中。 在 Monterey (12.0) 之前的 macOS 版本中,--apple-use-keychain--apple-load-keychain 标志分别使用语法 -K-A
  3. 如果您没有安装 Apple 的 ssh-add 标准版本,可能会收到错误消息。 有关详细信息,请参阅 “错误:ssh-add:非法选项 – apple-use-keychain
  4. 如果系统继续提示你输入密码,则可能需要将命令添加到 ~/.zshrc 文件(或 bash 对应的 ~/.bashrc 文件)
  1. 在后台启动 ssh 代理

    1
    2
    $ eval "$(ssh-agent -s)"
    > Agent pid 59566

    根据您的环境,您可能需要使用不同的命令。 例如,在启动 ssh-agent 之前,你可能需要通过运行 sudo -s -H 根访问,或者可能需要使用 exec ssh-agent bashexec ssh-agent zsh 运行 ssh-agent

  2. 将 SSH 私钥添加到 ssh-agent

    如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称

    1
    ssh-add ~/.ssh/id_ed25519

除了上述的方式生成密钥,还有 为硬件安全密钥生成新的 SSH 密钥 方式,这里不做说明,可以移步官方文档查看

将 SSH 密钥添加到 GitHub 账户

github-ssh-add

其他更多设置可参考官方文档 新增 SSH 密钥到 GitHub 帐户

将签名密钥告诉 Git

  • 如需全局配置:添加 --global 参数
  • 如果你有多个 Git 账号,推荐 按照不同的账号分别进行配置,具体可参考 Git 多账号配置 这篇文章
1
2
3
4
5
6
7
8
9
# 1. 配置 Git 使用 SSH 对提交和标记签名
git config gpg.format ssh
# 2. 配置指定 SSH 签名密钥
# 将 ~/.ssh/id_ed25519 替换为要使用的公钥路径
git config user.signingkey ~/.ssh/id_ed25519.pub
# commit 开启自动签名
git config commit.gpgsign true
# tag 开启自动签名
git config tag.gpgsign true

配置可信公钥列表

1
2
3
4
5
6
7
mkdir -p ~/.config/git
touch ~/.config/git/allowed_signers
# 将指定的密钥文件(~/.ssh/id_ed25519)内容复制到 allowed_signers 文件中
# 如果是继续追加, 将 > 替换为 >>
cat ~/.ssh/id_ed25519.pub > ~/.config/git/allowed_signers
# 配置可信公钥
git config gpg.ssh.allowedSignersFile "~/.config/git/allowed_signers"

对 commit 签名

1
2
3
4
# 1. 当本地分支中的提交更改时,请将 -S 标志添加到 git commit 命令
git commit -S -m "YOUR_COMMIT_MESSAGE"
# 2. 在本地完成创建提交后,将其推送到 GitHub 上的远程仓库
git push

对 tag 签名

注意:如果 Git 客户端配置为默认对提交进行签名,GitHub Desktop 仅支持提交签名

1
2
3
4
# 1. 若要对标记进行签名,请将 -s 添加到 git tag 命令
git tag -s MYTAG
# 2. 通过运行 git tag -v [tag-name] 验证已签名的标记
git tag -v MYTAG

GPG

具体实践可参考官方文档 GPG 提交签名验证

S/MIME

S/MIME 签名验证需 Git 2.19 及以上版本

由于 S/MIME 用的比较少,这里就不做具体的演示,可参考 官方文档

验证签名

1
2
3
4
# 查看一下本地签名信息
# 正常情况,在 commit 提交号下
# good "git" signature for $(email) with $(publicKey)
git log --show-signature

问题

验证签名提示异常

No signature:

no-signature

No principal matched

no-principal

invalid key

invalid-key

如何给现有密钥更新密码

通过输入以下命令,您可以 更改 现有私钥 的密码而无需重新生成密钥对

1
2
3
4
5
6
7
# 修改 id_ed25519 密钥密码
$ ssh-keygen -p -f ~/.ssh/id_ed25519
> Enter old passphrase: [Type old passphrase]
> Key has comment 'your_email@example.com'
> Enter new passphrase (empty for no passphrase): [Type new passphrase]
> Enter same passphrase again: [Repeat the new passphrase]
> Your identification has been saved with the new passphrase.

参考

  1. Git 提交使用 SSH 签名和 GPG 签名验证
  2. SSH 提交签名验证
  3. GitHub commit 签名指南
  4. 维护代码的尊严:GPG签名让你的Git commit不再裸奔
]]>
- - - <p><img src="https://docs.github.com/assets/cb-17614/mw-1440/images/help/commits/verified-commit.webp" alt="verified-commit"></p> -<p>通常 Push 代码到远程托管平台(GitHub,Gitlab,Gitee 等),需要提前在托管平台上传我们 Git 账户的公钥(*.pub),平台使用上传的公钥来验证身份(本地的 Git 私钥与平台上的公钥配对,以确保你有权限读写该仓库),该验证只会在 Push 时进行检查</p> - - - - - - - - -
- - - 重要说明 - - https://incoder.org/2022/06/05/top1/ - 2022-06-05T06:05:00.000Z - 2024-08-11T12:14:45.519Z - - 距离上一次更新文章已经过去了 1 年多了,时间可过的真快。原计划将现在的主站点要进行按照领域划分,将平时工作中遇到的、实践的技术整理到对应的领域,方便快速查找,提供一个沉浸式的学习知识体验,但由于个人也是一个懒癌拖更患者,加之在过去的一年工作中处在新领域,学习了很多技术,很多笔记还没有整理完善,因此没有及时更新博客,同时最近也遇到了站点无法正常访问,经过一系列的排查,发现了是由于开源的 CDN 提供方 jsdelivr 被污染了 DNS,因此不得不先更新站点的基础服务,以便能正常访问

话说,国内的网络环境真的是一言难尽,不得不说在技术、技术基础设施、技术思想等发展道路上任重道远

对于个人几个主要的站点,规划如下

  1. incoder.org: 作为分享生活、感悟、个人状态为主的地方,偶尔汇总某些技术等的综合性文章
  2. backend.incoder.org: 记录以 Java 为基础的后端开发生态技术领域
  3. mobile.incoder.org: 记录以原生开发为基础的移动端开发生态技术
  4. incoder.app: 记录个人开源应用

后续会迁移本站点部分文章到具体的领域站点

]]>
- - - <p>距离上一次更新文章已经过去了 1 年多了,时间可过的真快。原计划将现在的主站点要进行按照领域划分,将平时工作中遇到的、实践的技术整理到对应的领域,方便快速查找,提供一个沉浸式的学习知识体验,但由于个人也是一个懒癌拖更患者,加之在过去的一年工作中处在新领域,学习了很多技术,很多笔记还没有整理完善,因此没有及时更新博客,同时最近也遇到了站点无法正常访问,经过一系列的排查,发现了是由于开源的 CDN 提供方 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2pzZGVsaXZyL2pzZGVsaXZyL2lzc3Vlcy8xODM5Nw==">jsdelivr<i class="fa fa-external-link-alt"></i></span> 被污染了 DNS,因此不得不先更新站点的基础服务,以便能正常访问</p> - - - - - - - - -
- - - 评论不自由,赞美无意义 - - https://incoder.org/2021/05/05/freedom-pact/ - 2021-05-05T12:30:10.000Z - 2024-08-11T12:14:45.511Z - - 就像文章标题所述。每到三,五月这个时间,网络变得异常脆弱。各种“奇怪”的网站访问起来很费劲。对于一个技术人员,这些问题可以解决,但是每次都需要花费一定的时间和精力来应对这些,而且随着 GFW 的不断升级和加强,应对的策略和技术也是在不断迭代,前前后后已经出现了多种技术,本篇就以这个契机,梳理截止到 2021-05-05 所了解到关于 “代理” 相关的知识。现在主流的科学上网技术有 VPN、SS、SSR、V2Ray、Trojan、Trojan-Go,小众的 WireGuard、Brook、Snell 和 NaiveProxy 等

本篇文章大量使用了 一灯不是和尚 作者发布 《科学上网工具哪个好? 》文章中的内容,在此感谢作者对各技术的汇总以及经验总结,本篇文章是在原文章的基础上进行的扩展补充

VPN

虚拟专用网络(Virtual Private Network,缩写:VPN)是常用于连接中,大型企业或团体间私人网络的通讯方法。它利用隧道协议(Tunneling Protocol)来达到发送端认证,消息保密与准确性等功能。

VPN 只是一个统称,它有多种具体实现。比如:

  • PPTP(点对点隧道协议:Point to Point Tunneling Protocol);
  • L2TP(第二层隧道协议:Layer Two Tunneling Protocol);
  • IPsec(互联网安全协议:Internet Protocol Security);
  • WireGuard(一种协议);
  • OpenVPN(一种虚拟专用网络 Virtual Private Network(VPN)系统);
  • IKEv2(因特网密钥交换:Internet Key Exchange) 等

WireGuard

WireGuard 是由 Jason A. Donenfeld 开发,是最新的协议实现(在 2020 年,WireGuard 协议已被添加到 Linux 和 Android 内核中,从而为 VPN 提供商所采用。默认情况下,WireGuard 使用 Curve25519 进行秘钥交换,并使用 ChaCha20 进行加密,但还具有客户端和服务器之间预共享对称秘钥的功能)

OpenVPN

OpenVPN 是由 James Yonan 编写,实现了在路由或桥接配置和远程访问设施中创建安全点对点或站点对站点连接的技术。它实现了客户端和服务器应用程序。它不与IPsec兼容

OpenVPN 允许对等方使用预先共享的密钥、证书或用户名/密码相互验证。当在多客户端服务器配置中使用时,它允许服务器使用签名和证书颁发机构为每个客户端发布身份验证证书。

SS

SS 是 Shadowsocks 的缩写,中文名为影梭,为了避免关键词过滤,网友喜欢将 Shadowsocks 称为 “酸酸”,是一种基于 Socks5 代理方式的加密传输协议,也可以指定实现这个协议的各种开发包。Shadowsocks 是由 Clowwindy 为了自己使用谷歌查资料而编写;Shadowsocks 分为服务端和客户端,在使用之前,需要先将服务器端程序部署到服务器上面,然后通过客户端连接并创建本地代理。后来,他觉得这个东西非常好用,速度也很快,于是将源码提交到了 GitHub。由于其优秀的使用体验,Shadowsocks 被广泛传播,导致作者被某部门请去 “喝茶”。迫于压力 Clowwindy 于 2015-08-22 宣布停止维护此项目,并移除其个人页面所存储的源代码,而且保证永不再参与维护更新

虽然 Clowwindy 被迫放弃了 Shadowsocks,但开源界没有放弃,各路大神依旧在为 Shadowsocks 添砖加瓦,这就是开源的力量,倒下一个 Clowwindy,会有千千万万个 “Clowwindy” 站出来

SSR

SSR 是 ShadowsocksR 的缩写,网名爱称 “酸酸乳”,是在 Shadowsocks 的作者被请去喝茶之后,网名为 breakwa11 的用户发起的 Shadowsocks 的一个分支版本,它在 Shadowsocks 的基础上增加了一些数据混淆方式,修复了部分安全问题并提高了 QoS 优先级。由于 ShadowsocksR 在协议和混淆方面做了改进,更加不容易被 GFW 检测到,而且兼容原 Shadowsocks,并为新项目命名为 Shadowsocks-R,一开始部分代码由社区人员进行更新。由于不完全开源,也导致后来使用 SS 和 SSR 的用户分为两个阵营,互相撕逼,直到开发者 breakwa11 被人肉出来。breakwa11 最终决定删除 Shadowsocks-R 项目的所有代码,并解散了所有相关群组

事件始末澄清

ShadowsocksR 的作者一开始曾有过违反 GPL 协议,在发布二进制文件时不开放源码的争议。不过后来 Shadowsocks-R 项目由 breakwa11 采用了与 Shadowsocks 相同的 GPL、Apache、MIT 等多重自由软件许可协议

  • 2017-07-19,ShadowsocksR 作者 breakwa11 在 Telegram 频道 ShadowsocksR news 里转发了深圳市启动 SS 协议检测的消息并被大量用户转发,在电报(TG)圈引发恐慌。
  • 2017-07-24,breakwa11 遭到自称 “ESU.TV” 的不明省份人士人身攻击,对方宣传如果不停止开发并阻止用户讨论此事件将发布更多包含个人隐私的资料,随后 breakwa11 表示遭到对方人肉搜索并公开个人资料。为防止对方继续伤害无关人士,breakwa11 删除了 GitHub 上的所有代码、解散相关交流群组,并停止 ShadowsocksR 项目

从本质上来说,Shadowsocks 与 ShadowsocksR 的基本原理相同,都是基于 Socks5 的代理工具,只在本地客户端和服务器对数据包加解密,然后使用 Socks5 协议转发加密的数据包,而不用在乎使用什么协议,所以 Socks5 代理比其他应用层代理速度要快的多

Socks5

这里顺带科普一下 Socks5,Socks5 代理的原理是把你的网络数据请求先发送到你的代理服务器,然后由代理服务器转发给目标;如果目标有反馈发送到代理服务器,那么代理服务器会将数据包直接传回到你的本地网络,整个过程只需要数据的二次传输,并没有额外的处理。

示例:现在呢在深圳,你的代理服务器在香港,如果你想要访问 Google,那么你首先需要把数据请求通过本地 Socks5 代理客户端发给在你在香港的服务器上的 Socks5 代理服务端,然后你在香港的服务器将数据请求发送给 Google,再把 Google 反馈的结果传到你香港的代理服务器,然后通过 Socks5 服务端回传到本地的 Socks5 客户端,这样就可以绕开 GFW 的检测而实现科学上网

显而易见,Socks5 代理的所有数据走的任然是公网,而且在公网传输过程中,没有对数据进行任何加密和混淆,这跟 VPN 在公网建立虚拟专用通道传输过程中,对数据高强度加密的方式完全不同。Shadowsocks 和 ShadowsocksR 只在客户端和服务器端对数据做了简单加密和认证,主要功能是流量转发,过强才是主要目的。虽然 ShadowsocksR 已经停止更新很久了,而 Shadowsocks 仍处于社区人员的更新和维护之中,不断修复漏洞并增加新功能,所以现在 Shadowsocks 比 ShadowsocksR 更强大

提醒

不要迷信 SSR 一定比 SS 强,也包括现在的 V2Ray,Trojan,甚至是 WireGuard 等,因为增加混淆意味着损失速度,混淆加密越是强悍,那么其速度和稳定性损失就越大,另外 SSR 至今已被研究透了,而且长时间没有更新维护,其流量特征是可以被 GFW 精准识别,所以用 SSR 和 SS 没有本质区别,由于 SS 一直更新维护,反而更稳定。

V2Ray

V2Ray 是在 Shadowsocks 被封杀后,为表示抗议而开发,属于后起之秀,功能更加强大,为抗 GFW 封锁而生。V2Ray 现在已经是 Project V 项目的核心工具。而 Project V 是一个平台,其中也包括支持 Shadowsocks 协议。由于 V2Ray 早于 Project V 项目,且名声更大,我们习惯称 Project V 项目为 V2Ray,所以我们平常所说的 V2Ray 其实就是 Project V 这个平台,也就是一个工具集。其中,只有 VMess 协议是 V2Ray 社区原创的专属加密通讯协议

V2Ray 目前支持一下协议(截止 2019-12)

  • Blackhole: 中文名称“黑洞”,是一个出站数据协议,它会阻碍所有的数据的出站,配合路由(Routing)一起使用,可以访问被封杀的网站
  • DNS: 是一个出站协议,主要用于拦截和转发 DNS 查询。此出站协议只能接收 DNS 流量(包含基于 UDP 和 TCP 协议的查询),其它类型的流量会导致错误。在处理 DNS 查询时,此出站协议会将 IP 查询(即 A 和 AAAA)转发给内置的 DNS 服务器。其它类型的查询流量将被转发至原本的目标地址,DNS 出站协议在 V2Ray 4.15 中引入
  • Dokodemo-door: 中文名称“任意门”,是一个入站数据协议,它可以监听一个本地端口,并把所有进入此端口的数据发送到指定服务器的一个端口,从而达到端口映射的效果
  • Freedom: 是一个出站协议,可以用来向任意网络发起(正常的)TCP 或 UDP 数据
  • HTTP: 超文本传输协议,是传统的代理协议
  • Socks: 标准的 Socks 协议实现,兼容 Socks 4Socks 4aSocks 5『🙃目前无法正常访问』,也属于是一种传统的代理协议
  • VMess: 是 V2Ray 专用的加密传输协议,它分为入站和出站两部分,通常作为 V2Ray 客户端和服务器之间的桥梁。因为增加了混淆和加密,据说比 Shadowsocks 更安全。VMess 依赖于系统时间,请确保使用 V2Ray 的系统 UTC 时间误差在 90 秒之内,与时区无关。在 Linux 系统中可以安装 ntp 服务来自动同步系统时间
  • Shadowsocks: 包含入站,出站两部分协议,兼容大部分其它版本的实现。最早被个人开发的科学上网梯子协议,但 V2Ray 目前不支持 ShadowsocksR
  • Trojan: 特洛伊木马服务器如何对有效的特洛伊木马协议和其他协议(可能是 HTTPS 或任何其他探针)做出反应
  • VLESS: 是一个无状态的轻量传输协议,它分为入站和出站两部分,可以作为 V2Ray 客户端和服务器之间的桥梁,与 VMess 不同,VLESS 不依赖与系统时间,认证方式同样为 UUID,但不需要 alterId
  • Loopback: 是一个出站协议,可使出站连接被重新路由,最低支持版本 v4.36.0+

截止到 2021-05,V2Ray 可选的传输层配置有:TCP、mKCP,WebSocket、HTTP/2,DomainSocket、QUIC、gRPC。其中 mKCP、QUIC 和 TCP 用于优化网络质量;WebSocket 用于伪装;HTTP/2 和 DomainSocket 用于传输以及 TLS 加密

V2Ray 不仅可以在传输层配置 TLS 使用 HTTP 和 Socks 变成 HTTPS 和 Socks over TLS 协议,也可以使用 MTProto、Shadowsocks 和 VMess 通过传输层配置 TLS 加密伪装成 TLS 流量。所以,VMess 配置是 TLS 加密的最常见的做法,但没人会对 Shadowsocks 使用 TLS 加密,因为完全没有意义

客户端

V2Ray 客户端

Xray 与 XTLS

Xray 与 V2Ray 完全类同,Xray 是 Project X 项目的核心模块。因为 Xray 和 XTLS 黑科技 的作者 rpfx 曾是 V2fly 社区的重要成员,所以 Xray 直接 fork 全部 V2Ray 的功能,然后进行性能优化,并增加了新的功能,使 Xray 在功能上成为了 V2Ray 的超集,且完全兼容 V2Ray。

简而言之,Xray 是 V2Ray 的项目分支,Xray 是 V2Ray 的超集,就跟 Trojan-Go 和 Trojan-GFW 的关系类似,而且 Xray 性能更好,速度更快,更新迭代也更频繁。

由于 V2Ray-core 4.33.0 版本起,删除了 XTLS 黑科技,但任然支持 VLESS,所以是否原生支持 XTLS 是 Xray 和 V2Ray 最大的区别之一

Trojan 与 Trojan-Go

Trojan 原特指特洛伊木马,是一种计算机病毒程序。但是,我们今天所说的 Trojan 是一种新型的科学上网技术,全称为 Trojan-GFW,是目前最成功的科学上网伪装技术之一。你可以认为 Trojan 是 V2Ray 的 “WS + TLS” 模式的精简版,速度比 V2Ray 更快,伪装比 V2Ray 更逼真,更难以备 GFW 识别

Trojan 工作原理:Trojan 通过监听 443 端口,模仿互联网上最常见的 HTTPS 协议,把合法的 Trojan 数据伪装成正常的 HTTPS 通信,并真正地完整完成 TLS 握手,以诱骗 GFW 认为它就是 HTTPS,从而不被识别。Trojan 处理来自外界的 HTTPS 请求,如果是合法的,那么为该请求提供服务,否则将该流量交给 Caddy、Nginx 等 Web 服务器,由 Caddy、Nginx 等为其提供网页访问服务。基于整个交互过程,这样能让你的 VPS 更像一个正常的 Web 服务器,因为 Trojan 的所有行为均为 Caddy、Nginx 等 Web 服务器一致,并没有引入额外特征,从而达到难以识别的效果

Trojan-Go 是 Trojan-GFW 的分支项目,对 Trojan 进行性能优化,并增加不少新特性,Trojan-Go 性能和功能均有大幅度的提升,而且支持分流和 CDN

总结

通过上述,以及其他信息来源对这些技术有了一定的基本认识,从以下的几个方面来进行总结

原理不同

VPN:强调对公网传输过程中数据的加解密
SS/SSR/V2Ray/Xray/Traojan:专注于在客户端和服务器间加密,公网传输过程中特征没有 VPN 明显

目的不同

VPN:是走在公网中自建的虚拟专用通道,使用强大的加解密算法,为数据传输安全性、私密性而生,被广泛应用于企业、高校、科研部门等远程数据传输领域
SS/SSR/V2Ray/Xray/Trojan/Trojan-Go:是为了数据能够安全通过 GFW 而生,更强调的是对数据的混淆和伪装,加解密只是为了更好的隐藏数据特征而顺利通过 GFW 的检测

项目诞生的大致顺序

VPN > SS > SSR/V2Ray/WireGuard > Trojan/Trojan-Go > Xray

致每一个追求真理的人

凡事都有两面性,看如何去看待。其实我一直认为,有墙确实是一件好事,毕竟祖国互联网发展也不过 20 多年,在一定程度上过滤掉了一些没有自我认知,自我思考盲目跟风,觉得国外的月亮圆,国外什么都好的一群人;保障了在互联网开始萌芽的本土企业(毕竟国外的产品和技术都已经发展了好几轮,从解决问题的能力,使用的用户体验(不含本地化)等等方面都是完全甩开本地企业的服务)发展,提供给企业和网民一起成长的安全环境,在这一点上 GFW 功不可没。但作为一个技术从业人员来说,其中大部分的技术理念思想,技术平台等纯技术领域相关的东西来说不是那么友好,常常伴随着由于网络原因而造成的各种乱七八糟的问题,对于暂无本土相关替代服务支持时,在一定程度上阻碍了国内相关技术的发展进度和创新。

真如前面所述,凡事都有两面性。对于未知的事物,人类本能的会产生恐惧,因为它不可控,而互联网正真不可控的不是技术而是人,人是复杂的个体,一个技术的好坏不是它本身,而是使用的人在做什么样的事,而做的事在一定的环境下它是有好坏之分的。因此我在这里对自我约束,仅为获取相关学习的知识,不参与散布谣言、政治相关等言论,踏踏实实搞技术

希望未来的有一天,国内技术人员的输出是全球技术的风向标

参考

  1. 试用 Always Free 云服务
  2. 申请 Oracle Cloud 永久免费服务
  3. 2021年申请永久免费甲骨文云 Oracle Cloud 并创建实例最全攻略
  4. 多种科学上网指导教程汇总
  5. V2ray XTLS黑科技
]]>
- - - <p>就像文章标题所述。每到三,五月这个时间,网络变得异常脆弱。各种“奇怪”的网站访问起来很费劲。对于一个技术人员,这些问题可以解决,但是每次都需要花费一定的时间和精力来应对这些,而且随着 <span class="exturl" data-url="aHR0cHM6Ly9iYWlrZS5iYWlkdS5jb20vaXRlbS9HV0Y=">GFW<i class="fa fa-external-link-alt"></i></span> 的不断升级和加强,应对的策略和技术也是在不断迭代,前前后后已经出现了多种技术,本篇就以这个契机,梳理截止到 2021-05-05 所了解到关于 “代理” 相关的知识。现在主流的科学上网技术有 VPN、SS、SSR、V2Ray、Trojan、Trojan-Go,小众的 WireGuard、Brook、Snell 和 NaiveProxy 等</p> - - - - - - - - -
- - - OSS 初体验 - - https://incoder.org/2021/03/27/oss/ - 2021-03-27T09:44:46.000Z - 2024-08-11T12:14:45.515Z - - 在之前 SpringBoot(十二)文件上传 文章中,已经学习了使用 SpringBoot 基础的功能,完成静态资源的管理,本片文章我们同样也是对非结构化的静态数据进行管理,不过这次我们使用的是比较常用的 OSS 服务,废话不说,我们一起开始 OSS 之旅吧

什么是 OSS

OSS 是一种面向海量数据规模的分布式存储服务,具有稳定,可靠,安全,低成本的特点。主要用来存储各种非结构化的数据,比如视频,图像,日志,文本文件等。OSS 服务提供标准的 RESTful API 接口,并提供一些常用语言的 SDK 包,方便开发者进行快速开发和二次处理

常用的 OSS

市面上提供云服务的厂商有很多,这里以阿里云的 OSS 服务为主来,完成 OSS 相关的学习和实践

依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.aliyun.oss/aliyun-sdk-oss -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.11.1</version>
</dependency>

OSS 工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* OSS 文件上传
*
* @author : Jerry xu
* @since : 2020/11/3 09:12
*/
@Slf4j
public class OssUtils {

// 访问域名
private static final String ENDPOINT = "xxxxx";
// 存储空间
private static final String BUCKET_NAME = "xxxxx";
//==================访问密钥==================
private static final String ACCESS_KEY_ID = "xxxxx";
// 用户用于加密签名字符串和OSS用来验证签名字符串的密钥,必须保密
private static final String ACCESS_KEY_SECRET = "xxxxx";

/**
* 通过文件上传图片
*
* @param file 文件
* @return 上传结果地址
*/
public static String uploadFileByFile(File file) {
// // NIO 方式
// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());
// // 其他方式
// byte[] fileByte = Files.readAllBytes(Paths.get(file.getPath()));
// return uploadFileByByte(fileByte, file);
URL url;
String urlStr = null;
String[] split = new String[0];
try {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
// 获取文件名
String fileName = file.getName();
ossClient.putObject(BUCKET_NAME, fileName, file);
// 设置过期时间
url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, new Date(System.currentTimeMillis() + 3600 * 24 * 365 * 10));
log.info("原始图片地址:{}", url);
urlStr = url.toString();
// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());
// PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, "test", new ByteArrayInputStream(fileByte));
// // 不设置过期时间
// PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);
// // 去除过期时间参数地址
// split = urlStr.split("\\?");
} catch (Exception e) {
log.warn(e.getMessage());
}
return urlStr;
}

/**
* 通过字节数组上传图片
*
* @param binaryBytes 字节数组
* @param fileName 文件名
* @return 上传结果地址
*/
public static String uploadFileByByte(byte[] binaryBytes, String fileName) {
InputStream inputStream = new ByteArrayInputStream(binaryBytes);
return uploadFileByInputStream(inputStream, fileName);
}

/**
* 通过输入流上传图片
*
* @param inputStream 输入流
* @param fileName 文件名
* @return 上传结果地址
*/
public static String uploadFileByInputStream(InputStream inputStream, String fileName) {
URL url;
String urlStr = null;
String[] split = new String[0];
try {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
// String fileName = UUID.randomUUID().toString() + ".jpeg";
ossClient.putObject(BUCKET_NAME, fileName, inputStream);
// 设置过期时间
url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, new Date(System.currentTimeMillis() + 3600 * 24 * 365 * 10));
log.info("原始图片地址:{}", url);
urlStr = url.toString();
// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());
// PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, "test", new ByteArrayInputStream(fileByte));
// // 不设置过期时间
// PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);
// // 去除过期时间参数地址
// split = urlStr.split("\\?");
} catch (Exception e) {
log.warn(e.getMessage());
}
return urlStr;
}

}

上传

这里我们写一个上传接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ApiOperation("文件上传", notes = "支持多图上传")
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public List<String> uploadTest(@RequestParam("file") List<MultipartFile> file) {
List<String> uploadList = new ArrayList<>(file.size());
file.forEach(t -> {
try {
String url = OssUtils.uploadFileByInputStream(t.getInputStream(), t.getOriginalFilename());
uploadList.add(url);
} catch (IOException e) {
e.printStackTrace();
}
});
return uploadList;
}

测试

不废话了,直接看图就好了

问题

  1. 对于上传获取到的文件地址是一个会过期的地址,并不是一个固定不变的地址,如上截图所示,我偷懒直接将地址链接出的相关参数删去,拿到了一个永久存储的访问连接地址。但这里需要注意,这需要在你的 OSS 管理后台去设置你的文件存储的过期策略。这里就不进行截图演示了(主要是我没有登录系统的账号密码,逃 ~)
  2. 对于上传的文件我没有自定义文件名,这里有个问题是当用户上传 OSS 服务中已经存在的文件名的文件时,新上传的会覆盖旧文件,因此这个地方需要根据实际的业务场景选择合适的方式。在 OssUtils 工具类中我已经注释掉了将文件名重命名的代码,你可以在此处按照你的业务进行更改
  3. 第三个问题就是结合上面的两点的汇总方案,其实呢,对于一般的系统,这些静态资源就存永久的连接地址即可。但目前新的系统对用户的资料等也有了 “稍微” 高一点的保护,就是这些资源都是有时效性的,获取的地址就是我们上传拿到的原始地址,而我们存放在数据库中当然也不会是之前那种永久的连接地址,而是对应图片的一个唯一标识信息(可以是重命名后的文件名或者其他能够唯一标识资源你的字段),然后用户访问这些资源时,用存放在数据库中的唯一标识去 OSS 服务上查询对应的资源,然后加载这个地址去显示。

参考

  1. 阿里云对象存储 OSS
  2. 对象存储 Kodo
  3. 华为云对象存储服务 OBS
]]>
- - - <p>在之前 <a href="https://incoder.org/2021/03/10/springboot12">SpringBoot(十二)文件上传</a> 文章中,已经学习了使用 SpringBoot 基础的功能,完成静态资源的管理,本片文章我们同样也是对非结构化的静态数据进行管理,不过这次我们使用的是比较常用的 OSS 服务,废话不说,我们一起开始 OSS 之旅吧</p> - - - - - - - - -
- - - OSS 之 Minio 初体验 - - https://incoder.org/2021/03/16/minio/ - 2021-03-16T15:30:10.000Z - 2024-08-11T12:14:45.515Z - - MinIO 是一个基于 Apache License v2.0 开源协议使用 Go 语言开发的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

MinIO 包含 MinIO Server, MinIO Client 以及方便开发基于不同编程语言使用的 MinIO SDK,这三部分组成,使用步骤也很简单,在服务器上安装 MinIO Server 应用,在项目中集成对应的 MinIO SDK,然后按照你的业务情况编写相应的实现即可,在开始前,我们先看看为什么我选择 MiniIO 作为自建的 OSS 服务

  1. MinIO 由良好的存储机制
  2. 兼容 Amason 的 S3 分布式存储
  3. 天然的支持云原生
  4. 支持私有部署,可分布式,可单机,100%开源
  5. 友好简单的部署方式,提供管理页面
  6. 还可以配合其他的健康管理工具进行监控,比如 Prometheus

安装

由于 MinIO Server 已经提供了 Docker 的安装镜像,那我们就以 Docker 安装为例,其他安装方式可参考官方教程 MinIO Quickstart Guide

关于 Docker 的安装这里不再赘述,Docker 相关详细的使用等知识,可参考我之前的文章 Docker(一)

1
2
3
4
5
6
7
8
# 1. 拉取 minio docker 镜像
docker pull minio/minio
# 2. 运行 minio 服务
docker run -p 9000:9000 --name minio \
-v /opt/docker/minio/data:/data \
-v /opt/docker/minio/config:/root/.minio \
-d --restart=always \
-d minio/minio server /data

这里简单说一下命令的含义,应用命名为 minio ,运行服务在 9000 端口,同时将容器的相关路径文件映射到宿主机的 /opt/docker/minio 路径,开机自启

成功运行服务,可查看日志

1
2
3
4
5
6
7
8
9
10
Endpoint: http://172.17.0.2:9000 http://127.0.0.1:9000
Browser Access:
http://172.17.0.2:9000 http://127.0.0.1:9000
Object API (Amazon S3 compatible):
Go: https://docs.min.io/docs/golang-client-quickstart-guide
Java: https://docs.min.io/docs/java-client-quickstart-guide
Python: https://docs.min.io/docs/python-client-quickstart-guide
JavaScript: https://docs.min.io/docs/javascript-client-quickstart-guide
.NET: https://docs.min.io/docs/dotnet-client-quickstart-guide
Detected default credentials 'minioadmin:minioadmin', please change the credentials immediately using 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD'

安装完成后,我们就可以通过 http://localhost:9000 访问 MinIO 服务,默认用户名和密码分别为: minioadmin, minioadmin

使用

页面操作

我们直接看图,输入账号密码后,可以看到 MinIO 的管理页面,我们就可以上传文件,是不是很方便。第一次上传必须先要创建一个 bucket 后,才可以上传,如下图操作结果

Client 操作

SDK 操作

这里以 Java 语言为例,查看官方文档时,一定要查看英文文档,中文文档已年久失修落后很多,其他的语言实现请参考官方文档

导入依赖

1
2
3
dependencies {
implementation "io.minio:minio:8.1.0"
}

功能实现

由于我这里是 SpringBoot 项目,为了方便在应用的 application.yml 文件中配置了 MinIO 相关的参数

配置文件

1
2
3
4
5
6
7
8
9
10
11
minio:
# minio 服务运行的地址
endpoint: http://127.0.0.1
# minio 服务运行的端口
port: 9000
# minio 服务登录账号
accessKey: minioadmin
# minio 服务登录密码
secretKey: minioadmin
# minio 设置上传默认存放桶
bucketName: cpe-manager-test

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/**
* 资源上传工具类
*
* @author : Jerry xu
* @since : 2021/3/18 14:05
*/
@Slf4j
@Component
public class MinioUtils {

/**
* minio:
* endpoint: http://192.168.1.163
* port: 9000
* accessKey: minioadmin
* secretKey: minioadmin
* bucketName: cpe-manager-test
*/
@Value("${minio.endpoint}")
private static final String ENDPOINT = "http://192.168.1.163";
@Value("${minio.port}")
private static final Integer PORT = 19000;
@Value("${minio.accessKey}")
private static final String ACCESS_KEY = "minioadmin";
@Value("${minio.secretKey}")
private static final String SECRET_KEY = "minioadmin";
@Value("${minio.bucketName}")
private static final String BUCKET_NAME = "cpe-manager-test";

private static MinioClient minioClient;

public static MinioClient getInstance() {
if (minioClient == null) {
minioClient = MinioClient.builder().endpoint(ENDPOINT, PORT, false).credentials(ACCESS_KEY, SECRET_KEY).build();
}
return minioClient;
}

/**
* 获取minio所有的桶
*
* @return java.util.List<io.minio.messages.Bucket>
* @throws Exception exception
*/
public static List<Bucket> getAllBucket() throws Exception {
// 获取minio中所以的 bucket
List<Bucket> buckets = getInstance().listBuckets();
for (Bucket bucket : buckets) {
log.info("bucket 名称: {} bucket 创建时间: {}", bucket.name(), bucket.creationDate());
}
return buckets;
}

/**
* 将图片上传到minio服务器
*
* @param inputStream 输入流
* @param objectName 存储的文件名称,必须包含后缀
* @param bucketName 自定义存储桶
*/
public static String uploadToMinio(InputStream inputStream, String objectName, String bucketName) {
try {
// 获取文件后缀
String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf("."));
String contentType = FileType.getContentType(fileSuffix);
// // 重新生成文件名,避免重复
// String objectName = UUID.randomUUID().toString() + fileSuffix;
long size = inputStream.available();
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(inputStream, size, -1)
.contentType(contentType)
.build();
// 上传到minio
ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs);
inputStream.close();
if (!StringUtils.isEmpty(objectWriteResponse.etag())) {
// 返回上传获取到的地址
return getUrlByObjectName(objectName);
}
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 将图片上传到minio服务器,默认存放在 cpe-manager-test 桶内
*
* @param inputStream 输入流
* @param objectName 存储的文件名称,必须包含后缀
*/
public static String uploadToMinio(InputStream inputStream, String objectName) {
try {
// 获取文件后缀
String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf("."));
String contentType = FileType.getContentType(fileSuffix);
// // 重新生成文件名,避免重复
// String objectName = UUID.randomUUID().toString() + fileSuffix;
long size = inputStream.available();
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(objectName)
.stream(inputStream, size, -1)
.contentType(contentType)
.build();
// 上传到minio
ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs);
inputStream.close();
if (!StringUtils.isEmpty(objectWriteResponse.etag())) {
// 返回上传获取到的地址
return getUrlByObjectName(objectName);
}
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 根据指定的objectName获取下载链接,需要bucket设置可下载的策略
*
* @param objectName 对象的名称
* @return java.lang.String
*/
public static String getUrlByObjectName(String objectName) {
try {
return getInstance().getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(BUCKET_NAME)
.object(objectName)
// 过期策略【默认有效期7天】
// .expiry(2, TimeUnit.HOURS)
.build());
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 根据objectName从minio中下载文件到指定的目录
*
* @param objectName minio上的文件名称
* @param fileName 下载生成的文件名
* @param dir 文件目录
* @throws Exception exception
*/
public static void downloadFromMinioToFile(String objectName, String fileName, String dir) throws Exception {
GetObjectArgs objectArgs = GetObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(objectName)
.build();
File file = new File(dir);
if (!file.exists()) {
if (file.mkdirs()) {
log.error("创建失败");
}
}
InputStream inputStream = getInstance().getObject(objectArgs);
FileOutputStream outputStream = new FileOutputStream(new File(dir, fileName.substring(fileName.lastIndexOf("/") + 1)));
int length;
byte[] buffer = new byte[1024];
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
outputStream.close();
inputStream.close();
}

/**
* 根据文件名批量删除(默认删除 BUCKET_NAME 下的文件)
*
* @param listFile 文件名(含后缀)列表,例如:demo.png
* @return 成功返回为null, 失败返回Map<objectName, failMessage>
*/
@SneakyThrows
public static Map<String, String> removeObjects(List<String> listFile) {
List<DeleteObject> objects = new LinkedList<>();
Map<String, String> resultMap = new HashMap<>();
listFile.forEach(t -> objects.add(new DeleteObject(t)));
Iterable<Result<DeleteError>> results =
getInstance().removeObjects(
RemoveObjectsArgs.builder()
.bucket(BUCKET_NAME)
.objects(objects)
.build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
resultMap.put(error.objectName(), error.message());
log.error("Error in deleting:{}, message{}", error.objectName(), error.message());
}
return resultMap;
}

}

上传接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation(value = "文件上传", notes = "支持多文件上传")
public List<String> uploadTest(@ApiParam(value = "文件") @RequestParam("file") List<MultipartFile> file) {
// 上传的图片地址
List<String> successFile = new ArrayList<>(file.size());
file.forEach(t -> {
try {
String url = MinioUtils.uploadToMinio(t.getInputStream(), t.getOriginalFilename());
log.info("图片地址{}", url);
successFile.add(url);
} catch (IOException e) {
e.printStackTrace();
}
});
return successFile;
}

测试

问题

bucket命名

创建 bucket 时,命名不可以使用下划线符号 “_

账号密码修改

通过网页管理页面修改登录账号及密码,提示 “Credentials of this user cannot be updated through MinIO Browser.” ,原因是安装应用时,并未显示的指定用户名和密码,可在运行启动时添加如下配置

1
2
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \

最长7天有效

通过网页管理页面共享图片或者是使用 SDK 上传图片得到的图片 URL 地址,有效期最长为7天

1
2
3
4
5
6

mc config host add minio http://192.168.1.163:19000 minioadmin minioadmin --api S3v4
mc policy set public minio/cpe-manager-test

mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin --api S3v4
mc policy set public minio/bucket (bucket修改成你自己的名字)

图片无法查看

  1. 使用 SDK 上传时,需要注意设置content-type信息
  2. 无权限查看

小结

关于 MinIO 还有很多知识点,本片只是站在使用者角度,把一些使用过程和问题进行了汇总,谈不上深度

参考

  1. Minio 手册
  2. Minio 示例
  3. Minio 修改密码
  4. Minio
  5. Minio 安装以及使用
  6. Minio 设置文件链接永久有效
]]>
- - - <p>MinIO 是一个基于 Apache License v2.0 开源协议使用 Go 语言开发的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。</p> - - - - - - - - -
- - - 琅嬛福地 - - https://incoder.org/2021/03/06/scenically/ - 2021-03-06T21:50:11.000Z - 2024-08-11T12:14:45.515Z - - 在金庸武侠《天龙八部》中,“琅嬛福地”存放了无崖子和李秋水搜罗天下各门各派的武功,江湖人士练成这里的一门武功绝学,就能在江湖中有自己的一席之地。而这里存放了我计算机相关学习、实践应用,以及经常使用的一些网站资源

计算机网络

数据结构与算法

操作系统

计算机组成原理

编译原理

资源

参考

  1. 聊一聊我在B站上自学编程的经历吧
]]>
- - - <p>在金庸武侠《天龙八部》中,“琅嬛福地”存放了无崖子和李秋水搜罗天下各门各派的武功,江湖人士练成这里的一门武功绝学,就能在江湖中有自己的一席之地。而这里存放了我计算机相关学习、实践应用,以及经常使用的一些网站资源</p> - - - - - - - - -
- - - 微信小程序之 Vant实战(一) - - https://incoder.org/2021/02/12/wechat-mini1/ - 2021-02-12T11:11:11.000Z - 2024-08-11T12:14:45.519Z - - 过年正好时间比较集中,可以把之前的一个想法付诸实践,之前一直想给老爸做一个类似于账单管理的应用,方便他每天把客户需要物品记录成一个清单进行管理,其中主要包含已下功能点。其一,支持添加任务列表(账单);其二,支持任务列表分享(账单)。以上是我的第一期规划功能规划,话不多说我们就一起来跟着我来完成这个小程序的开发吧,本篇主要讲小程序的初始化相关工作

这是我第一次来开发小程序,虽然之前有开发 Android 客户端的经验,有一定的客户端经验,但是小程序却一直没有去实践,我主要是觉得小程序的使用体验真的很差。但随着现在人们的硬件设备越来越好,并且微信团队在应用底层也做了很多的扩展和优化,现在使用小程序开发轻量级的应用还是很方便且高效

环境及选型

  1. OS:macOS(11.2.1)
  2. IDE:WeChat Devtools(1.05.2102010)
  3. Node:v15.5.0
  4. Vant:1.6.7
  5. 微信小程序账号

关于账号的申请,这里不做讲解,请自行解决

初始化项目

创建项目

不废话,直接看图

项目结构

项目是一个基于云开发的方式,创建完成后会包含云相关的一些操作实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bill
├── cloudfunctions/ # 云函数管理【清空当前文件夹下的内容】
│ │── callback/
│ │── echo/
│ │── login/
│ └── openapi/
├── minprogram/
│ │── components/ # 组件【清空当前文件夹下内容】
│ │── images/ # 图片管理【清空当前文件夹下内容】
│ │── pages/ # 页面管理【除 index 页面,其余都删除】
│ │ │── addFunction/
│ │ │── chooseLib/
│ │ │── databaseGuide/
│ │ │── deployFunctions/
│ │ │── im/
│ │ │── index/
│ │ │ │── index.js
│ │ │ │── index.json
│ │ │ │── index.wxml
│ │ │ │── index.wxss
│ │ │ └── user-unlogin.png
│ │ │── openapi/
│ │ │── storageConsole/
│ │ └── userConsole/
│ │── style/ # 样式管理
│ │── app.js # 项目入口逻辑管理
│ │── app.json # 组件库配置
│ │── app.wxss # 全局样式设置
│ └── sitemap.json #
├── project.config.json # 项目配置文件
└── README.md # 项目说明

精简项目

从上面我们可知初始化的项目,包含了一些示例,我们对其精简

  1. 清空 cloudfunctions 目录下的云函数内容
  2. 根据项目结构里的备注,进行删除相关的文件
    • 清空 components 文件夹下的组件
    • 清空 images 文件夹中的内容
    • 删除 pages 文件夹下, index 的文件夹
    • 删除 index 文件夹下的 user-unlogin.png 文件
  3. 修改文件内容
    • 清空 index 文件夹下 index.wxml,index.wxss 文件中的内容
    • 修改 index 文件夹下 index.js 文件内容
    • 清空 minprogram 文件夹下 app.wxss 文件中的样式内容
  4. 修改配置
    • 移除 app.json 文件中 已经移除掉的 pages 的配置
    • 修改 project.config.json 文件,移除 miniprogram 的配置

添加 vant 组件

整个步骤,如下截图

执行命令根据官方提供的方式和自身喜好选择,我这里使用的是 yarn 命令进行安装相关的依赖

测验效果

这里以添加 Button 为例来查看是否生效

  1. 在 pages/index 路径下的 index.json 文件中,添加 vant 的 Button 组件
    1
    2
    3
    4
    5
    {
    "usingComponents": {
    "van-button": "@vant/weapp/button/index"
    }
    }
  2. 在 pages/index 路径下的 index.wxml 文件中,添加 vant 的相关组件
    1
    2
    3
    4
    <van-button type="primary">主要按钮</van-button>
    <van-button type="info">信息按钮</van-button>
    <van-button type="warning">警告按钮</van-button>
    <van-button type="danger">危险按钮</van-button>
  3. 编译,在模拟器中查看效果

对于引用的组件,是公共的,可以写在 app.json 文件中

参考

  1. 官方开发文档
  2. 微信小程序组件库Vant weapp的使用与weui零基础入门课程
]]>
- - - <p>过年正好时间比较集中,可以把之前的一个想法付诸实践,之前一直想给老爸做一个类似于账单管理的应用,方便他每天把客户需要物品记录成一个清单进行管理,其中主要包含已下功能点。其一,支持添加任务列表(账单);其二,支持任务列表分享(账单)。以上是我的第一期规划功能规划,话不多说我们就一起来跟着我来完成这个小程序的开发吧,本篇主要讲小程序的初始化相关工作</p> - - - - - - - - - - -
- - - 第 100 篇原创文章 - - https://incoder.org/2020/12/31/milepost1/ - 2020-12-31T17:26:20.000Z - 2024-08-11T12:14:45.515Z - - 未曾想过,居然能写到第 100 篇文章。虽然大部分文章都是线性流水操作,但全部是自己经过实践的总结;虽然没有精彩的故事,但都是自己成长的思考;虽然有时一篇文章需要要长达一个多月的反复核对,但还是能默默坚持。只是这第 100 篇来的有点晚,断断续续大概有 3 年的时间,时间是个坏老头,把我给你写情话,悄悄的改成了谎话!

回想起之前写文章主要是为了记录一些操作步骤和一些知识点,方便遇到类似问题,快速定位,解决问题。但随着文章的越写越多,包含的内容也越来越多,需要去了解的知识也越来越多,真的是有一种 “你知道的越多,你不知道的越多” 的感觉,这种感觉让我对待每个知识点都能有往深去深挖的动力,对每一个知识点用自己文字将它讲出来时有一种让我欲罢不能成就感,这或许就是上瘾吧

总结下第一个里程碑,主要是平时接触到领域算是一些入门级别的一些文章,以及一些比较粗浅的见闻,缺乏深层次的剖析和思考,这也是第二个里程碑首要做的事情,把每个接触到的知识点进行深挖,打通自己的技术栈。技术领域能不能走得远,很大程度上并不是你的技能宽度,而是深度,是在一个方向上的深耕,并且对于底层的技能也是要有足够的涉猎,只有这样就算是技术的花样任它怎么去变,你都能以不变应万变(透过现象找到本质);第二点是是对第一阶段内容完善补充;第三点就是打磨自己的语言表达能力,让文章更加的通俗易懂;第四点就是不能太拖拉,要保持高效的内容输出

这一阶段,对开源项目贡献评价最高的是 rap2-delos 项目了。努力在接下来的里程中,提高质量和参与度,争取早日在大型项目中做到 Committer

很感谢这一路走来,大伙对我的认可和期待以及赞赏,下一个里程碑我们见~

]]>
- - - <p>未曾想过,居然能写到第 100 篇文章。虽然大部分文章都是线性流水操作,但全部是自己经过<strong>实践</strong>的总结;虽然没有精彩的故事,但都是自己成长的<strong>思考</strong>;虽然有时一篇文章需要要长达一个多月的反复核对,但还是能默默<strong>坚持</strong>。只是这第 100 篇来的有点晚,断断续续大概有 3 年的时间,时间是个坏老头,把我给你写情话,悄悄的改成了谎话!</p> - - - - - - - - -
- - - SpringBoot 源码构建 - - https://incoder.org/2020/12/31/springboot11/ - 2020-12-31T10:24:00.000Z - 2024-08-11T12:14:45.515Z - - 前两天刚刚学习了 Gradle 构建 SpringBoot 项目,再查看官方文档时,得知 SpringBoot 从 Spring Boot 2.3.0.M1 版本开始完全切换到使用 Gradle 来构建项目,那么本篇文章就来实践,基于源码来编译构建 SpringBoot,话不多说,本次构建构建是 2020 年的最后一次发布的版本 2.4.1

环境

  • OS:macOS 11.1
  • JDK:JDK1.8
  • Gradle:6.7.1-bin
  • IDE:IntelliJ IDEA Community 2020.3

Gradle 版本通过 https://github.com/spring-projects/spring-boot/blob/master/gradle/wrapper/gradle-wrapper.properties 文件可知,使用的 6.7.1-bin,那么本地也使用该版本编译,对于 Gradle 的安装可参考 Gradle(一)基础 文章

获取源码

1
2
# 这里使用 cnpmjs 来提高 clone 速度
git clone https://github.com.cnpmjs.org/spring-projects/spring-boot.git

编译构建

使用 IDEA 打开项目,会自动创建索引以及,下载项目的依赖,由于依赖的 jar 比较多,建议使用 阿里云 镜像,关于 Gradle 怎么修改依赖镜像源,可参考 专治各种网络不服 文章,阿里云镜像能加速大部分的 jar,但有一部分在阿里云上并没有,你可以通过手动方式导入到本地

]]>
- - - <p>前两天刚刚学习了 Gradle 构建 SpringBoot 项目,再查看官方文档时,得知 SpringBoot 从 <span class="exturl" data-url="aHR0cHM6Ly9zcHJpbmcuaW8vYmxvZy8yMDIwLzA2LzA4L21pZ3JhdGluZy1zcHJpbmctYm9vdC1zLWJ1aWxkLXRvLWdyYWRsZQ==">Spring Boot 2.3.0.M1<i class="fa fa-external-link-alt"></i></span> 版本开始完全切换到使用 Gradle 来构建项目,那么本篇文章就来实践,基于源码来编译构建 SpringBoot,话不多说,本次构建构建是 2020 年的最后一次发布的版本 2.4.1</p> - - - - - - - - - - -
- - - Gradle(三)SpringBoot 单工程 - - https://incoder.org/2020/12/16/gradle3/ - 2020-12-16T13:30:46.000Z - 2024-08-11T12:14:45.515Z - - Gradle(一)基础 的文章中,我们已经对 Gradle 有了一定的认识,本篇来看一看在后端开发中使用 Gradle 构建 SpringBoot 项目的开发。通常有两种方式来构建项目,第一种:每个功能模块即是一个代码工程,用一个 Git 仓库来管理,每个模块只负责完成一件事情;第二种:整个系统的多个模块聚合在一个代码工程里面,也就是我们常说的多模块项目,本篇先来讲一讲单工程

工程选择

对于单工程,和聚合工程的选择主要根据你所在项目团队的大小,项目分工,以及项目的复杂程度等来考虑。

单工程:适用于项目分工明确,项目庞大复杂,架构服务边界划分明确,配套的自动化等设施完善
聚合工程:适用于项目人员不是很多,项目功能一般,需要一个人集中化管理等

环境

  • OS:macOS 11.1
  • JDK:JDK1.8
  • Gradle:6.7.1-bin
  • IDE:IntelliJ IDEA Community 2020.3
  • SpringBoot:2.4.1

build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 项目使用插件,可从 https://plugins.gradle.org 库中寻找合适的插件
plugins {
id 'org.springframework.boot' version '2.4.1'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}

// 这里和 maven类似,用于项目唯一坐标
group = 'com.example'
version = '0.0.1-SNAPSHOT'
// 项目兼容版本
sourceCompatibility = '1.8'

// 依赖第三方jar从哪个仓库去下载
repositories {
mavenCentral()
}

// 项目所需的第三方依赖
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

// 测试相关
test {
useJUnitPlatform()
}

一个 SpringBoot 项目基本的 build.gradle 文件由 plugins,项目坐标,repositories,dependencies,test 基础内容组成。关于 plugins 使用常见有两种方式,核心的依赖,是没有版本号,它和你使用的 Gradle 关联,你无需过多关系这些核心插件的依赖版本

1
2
3
4
5
6
7
// 旧方式
apply plugin: 'java'

// 新方式(推荐)
plubins {
id 'java'
}

settings.gradle

用于项目模块管理,由于这个单工程,这里只有一个模块

1
rootProject.name = 'demo'

多环境

可通过自定义 task 来出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// prod
tasks.register("bootRunProd") {
group = "application"
description = "Runs the Spring Boot application with the prod profile"
doFirst {
tasks.bootRun.configure {
systemProperty("spring.profiles.active", "prod")
}
}
finalizedBy("bootRun")
}

// dev
tasks.register("bootRunDev") {
group = "application"
description = "Runs the Spring Boot application with the dev profile"
doFirst {
tasks.bootRun.configure {
systemProperty("spring.profiles.active", "dev")
}
}
finalizedBy("bootRun")
}

启动方式

  • 方式一:图形化界面中,直接运行对应环境

  • 方式二:在命令行中,使用命令来运行对应环境,比如 gradlew bootRunDev

  • 方式三:当然你也可以在启动时指定你需要激活的环境

    1
    2
    # 这里激活的 test 环境,把 ${jar_name} 参数换成对应启动的应用文件
    java -jar ${jar_name} --spring.profiles.active=test

排除依赖

1
2
3
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}

打包

打包时需要,注意我们的 SpringBoot 应用它本质上是一个 bootJar(Fatjar) 应用,因此需要将应用打成一个 bootJar(Fatjar)。而对于什么是 bootJar 和 jar 的区别,可以查看之前在 SpringBoot(二) 启动分析JarLauncher 文章中对于 jar 规范 说明

打包方式

  • 方式一:图形化操作

  • 方式二:命名执行

    1
    2
    3
    # 在项目的根目录执行,Windows 使用:gradlew;Linux/macOS:./gradlew
    # 当然如果那你已安装且配置好 gradle 的环境,你可以直接使用 gradle 代替 ./gradlew 的相关命令
    gradlew bootJar

发布

参考

  1. SpringBoot+gradle 构建多模块项目
  2. IDEA 2020.2 + Gradle 6.6.1 + Spring Boot 2.3.4 创建多模块项目
  3. Spring-boot 2.3.x 源码基于Gradle编译
  4. 用 Gradle 构建 Spring Boot 项目
  5. 使用 Gradle 构建 springboot 多模块项目,并混合groovy开发
  6. Spring Boot Gradle Plugin Reference Guide
]]>
- - - <p>在 <a href="https://incoder.org/2020/12/10/gradle1/">Gradle(一)基础</a> 的文章中,我们已经对 Gradle 有了一定的认识,本篇来看一看在后端开发中使用 Gradle 构建 SpringBoot 项目的开发。通常有两种方式来构建项目,第一种:每个功能模块即是一个代码工程,用一个 Git 仓库来管理,每个模块只负责完成一件事情;第二种:整个系统的多个模块聚合在一个代码工程里面,也就是我们常说的多模块项目,本篇先来讲一讲单工程</p> - - - - - - - - -
- - - Gradle(二)Android - - https://incoder.org/2020/12/15/gradle2/ - 2020-12-15T09:00:46.000Z - 2024-08-11T12:14:45.515Z - - 在上一篇 Gradle 的文章中,已经对 Gradle 有了一定的认识,Gradle 在 Android 有着广泛的应用,用作 Android 包依赖管理,应用构建,测试,等一些列自动化,我们本篇就来了解下在 Android 领域 Gradle 的使用。其实 Android 项目结构和之前在第一篇 Gradle 项目结构基本相同,只是在 module 级别多了的 proguard-rules.pro。对于不管是 Android 项目或是 Spring 系列项目的子 module 都会有 build.gradle 文件

Project 级别

build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// gradle 脚本执行所需依赖,分别是对应的maven库和插件
buildscript {
repositories {
google()
jcenter()
}
// 声明依赖 Android Gradle 插件版本
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

// 项目所有 module 配置需要的依赖
allprojects {
repositories {
google()
jcenter()
}
}

// 一个 clean 任务,用于删除 build 目录的文件
task clean(type: Delete) {
delete rootProject.buildDir
}

settings.gradle

1
2
// 默认指的是创建 Android 项目生成的 app 模块,也是默认的应用启动模块
include ':app'

Module 级别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// 表示这是一个应用程序模块,可直接运行
apply plugin: 'com.android.application'

// 编译时间
static def releaseTime() {
return new Date().format('yyyy-MM-dd', TimeZone.getTimeZone('UTC'))
}

android {
// 编译 Android 版本
compileSdkVersion 29
// 默认配置
defaultConfig {
// 应用 ID,手机中用于识别应用的唯一标识
applicationId "org.incoder.android"
// 目标 Android 版本
targetSdkVersion 29
// 申明应用可超过 65536 的方法,可参考:https://developer.android.google.cn/studio/build/multidex?hl=zh_cn
multiDexEnabled true
// 申明要使用AndroidJUnitRunner进行单元测试
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

// 签名配置,相关信息放置在 gradle.properties 文件中
signingConfigs {
debug {
storeFile file(DEBUG_STORE_FILE)
storePassword DEBUG_STORE_PASSWORD
keyAlias DEBUG_KEY_ALIAS
keyPassword DEBUG_KEY_PASSWORD
}
release {
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
v2SigningEnabled true
}
}

buildTypes {
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
// 签名
// signingConfig signingConfigs.debug
manifestPlaceholders = [
//JPush
JPUSH_APPKEY : "",
JPUSH_CHANNEL: "",
// Pgy
PGYER_APPID : "7907554687e4c116316feedb3820ce52",
// Bugly
BUGLY_APPID : "",
VERSION_NAME : "0.1.0",
]
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}

release {
// 混淆
minifyEnabled false
// Zipalign优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources false
// 前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// 签名
signingConfig signingConfigs.release
// AppAnalytics key
manifestPlaceholders = [
// JPush
JPUSH_APPKEY : "",
JPUSH_CHANNEL: "",
// Pgy
PGYER_APPID : "7907554687e4c116316feedb3820ce52",
// Bugly
BUGLY_APPID : "",
VERSION_NAME : "0.1.0",
]
}
}

// 重命名安装包
android.applicationVariants.all {
variant ->
variant.outputs.all {
output ->
output.outputFileName = variant.flavorName + buildType.name +
"_" + releaseTime() + ".apk"
}
}

// 产品变种
flavorDimensions "minSDK"

// 针对不同渠道的配置
productFlavors {
// 测试环境渠道包
dev {
applicationId 'org.incoder.test'
minSdkVersion 19
// 测试环境IP配置 API 接口地址
buildConfigField 'String', 'API', '"http://xxx.xxx.xxx.xxx:8888"'
versionCode 2020122501
versionName "2.0"
// 指定产品变种
dimension "minSDK"
}
// 正式环境渠道包
rel {
applicationId "org.incoder.android"
minSdkVersion 16
// 正式环境域名 API 接口地址
buildConfigField 'String', 'API', '"http://api.xxx.xxx/"'
versionCode 2020122501
versionName "2.1"
// 指定产品变种
dimension "minSDK"
}
}

// 过滤指定产品变种(渠道,构建类型)
// variantFilter { variant ->
// def names = variant.flavors*.name
// def isDebug = variant.buildType.debuggable
// // To check for a certain build type, use variant.buildType.name == "<buildType>"
// if (names.contains("rel") && isDebug) {
// // Gradle ignores any variants that satisfy the conditions above.
// setIgnore(true)
// }
// }

// 多渠道配置
productFlavors.all {
flavor ->
flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
}

// 执行lint检查,有任何的错误或者警告提示,都会终止构建
lintOptions {
disable 'MissingTranslation', 'ExtraTranslation'
// abortOnError一定要设为false,这样即使有报错也不会停止打包了
abortOnError false
// 在打包Release版本的时候进行检测,可以打开,这样报错还会显示出来
checkReleaseBuilds false
}

dexOptions {
jumboMode true
javaMaxHeapSize "4g"
}

// 打包时的配置
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/rxjava.properties'
}

aaptOptions.cruncherEnabled = false
aaptOptions.useNewCruncher = false

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

// 项目依赖的包
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:support-v13:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:support-vector-drawable:28.0.0'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
testImplementation 'junit:junit:4.12'
}

关于 module 中的 build.gradle 配置文件中的各项已在示例中加入了注释说明,其中一些配置,这里再简单的说明下

apply plugin

这里的 apply plugin 有两种模式:

  1. com.android.application:表示这是一个应用程序模块
  2. com.android.library:表示这是一个库模块

前者可以直接运行,后着是依附别的应用程序运行

buildTypes

这里主要是生成安装文件的配置信息,一个 debug 类型,用于指定生成测试版安装文件配置,可忽略不写;另一个是 release,用于指定生成正式版安装文件的配置。

  • minifyEnabled:是否对代码进行混淆,默认 false
  • proguardFiles:指定混淆的规则文件,默认指定了 proguard-android.txt 文件和 proguard-rules.pro 文件。
    • proguard-android.txt:默认的混淆文件,里面定义了一些通用的混淆规则
    • proguard-rules.pro:位于当前项目的根目录下,可以在该文件中定义一些项目特有的混淆规则
  • buildConfigField:可用于解决不同渠道不同的服务地址,或不同渠道 LOG 打印控制等
  • debuggable:是否支持断点调试,release 默认为 false,debug 默认 true
  • jniDebuggable:是否可以调试 NDK 代码,使用 lldb 进行 C 和 C++ 代码调试,release 默认为 false
  • signingConfig:设置签名信息,通过 singingConfig.release 或 singingConfig.debug,配置相应的签名,但是添加此配置前需要先添加 singingConfig 闭包
  • renderscriptDebuggable:是否开启渲染脚本,就是一些 C 写的渲染方法,默认为 false
  • renderscriptOptimLevel:渲染等级,默认为 3
  • zipAlignEnabled:是否对 apk 包执行 zip 对齐优化,减少 zip 体积,提高运行效率,release 和 debug 都默认 true
  • pseudoLocalesEnabled:是否在 apk 中生成伪语言环境,帮助国际化,一般很少使用
  • applicationIdSuffix:和 defaultConfig 中配置一样,指在 applicationId 中添加一个后缀
  • versionNameSuffix:添加版本名称的后缀,一般使用较少

productFlavors

这个配置主要是解决应用发布在不同应用市场,而需要对不同应用市场做一些不同配置,比如包名,应用名,以及一些统计,而需要不同渠道统计 ID 等

packagingOptions

packagingOptions 常见的设置项有 exclude、pickFirst、doNotStrip、merge

  1. exclude:过滤掉某些文件或者目录不添加到APK中,作用于APK,不能过滤aar和jar中的内容
    1
    2
    3
    4
    packagingOptions {
    exclude 'META-INF/**'
    exclude 'lib/arm64-v8a/libmediaplayer.so'
    }
  2. pickFirst:匹配到多个相同文件,只提取第一个。只作用于APK,不能过滤aar和jar中的文件
    1
    2
    3
    4
    packagingOptions {
    pickFirst "lib/armeabi-v7a/libaaa.so"
    pickFirst "lib/armeabi-v7a/libbbb.so"
    }
  3. doNotStrip:可以设置某些动态库不被优化压缩
    1
    2
    3
    4
    packagingOptions{
    doNotStrip "*/armeabi/*.so"
    doNotStrip "*/armeabi-v7a/*.so"
    }
  4. merge:将匹配的文件都添加到APK中,和pickFirst有些相反,会合并所有文件
    1
    2
    3
    4
    packagingOptions {
    merge '**/LICENSE.txt'
    merge '**/NOTICE.txt'
    }

统一版本

应用由多个 module 构成,而不同地方引用的包,需要做到全局的统一时,可以创建一个 xxx.gradle 的文件(这里的 xxx,自行取一个表明含义的内容即可),然后在使用的地方时,统一调用定义的版本即可,使用步骤如下

  1. 创建 xxx.gradle 文件(一般放在项目的根目录,和顶级 build.gradle 文件在同一层级),并添加如下内容,可根据自身需要调整
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    ext {

    android = [
    compileSdkVersion: 29,
    buildToolsVersion: "29.0.2",
    minSdkVersion : 19,
    targetSdkVersion : 29,
    versionCode : 2020010102,
    versionName : "0.1.0"
    ]

    version = [
    androidSupportSdkVersion: "29.0.0",
    retrofitSdkVersion : "2.6.3",
    okhttpSdkVersion : "4.3.0",
    dagger2SdkVersion : "2.22.1",
    glideSdkVersion : "4.9.0",
    butterknifeSdkVersion : "10.2.1",
    rxlifecycle2SdkVersion : "2.2.1",
    espressoSdkVersion : "3.0.2",
    canarySdkVersion : "1.5.4"
    ]

    // Android support 与 AndroidX support 对比
    // https://developer.android.google.cn/jetpack/androidx/migrate

    // support 库说明
    // https://developer.android.com/topic/libraries/support-library/features?hl=zh-cn
    dependencies = [
    // support
    "appcompat" : "androidx.appcompat:appcompat:1.1.0",
    "annotations" : "androidx.annotation:annotation:1.0.0",
    "cardview-v7" : "androidx.cardview:cardview:1.0.0",
    "constraint-layout" : "androidx.constraintlayout:constraintlayout:1.1.3",
    "swiperefreshlayout" : "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0",
    "material" : "com.google.android.material:material:1.1.0",
    "viewpager" : "androidx.viewpager:viewpager:1.0.0",
    "recyclerview" : "androidx.recyclerview:recyclerview:1.1.0",
    "vectordrawable" : "androidx.vectordrawable:vectordrawable:1.1.0",
    "support-v4" : "androidx.legacy:legacy-support-v4:1.0.0",

    // network
    "retrofit" : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}",
    "retrofit-converter-gson" : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}",
    "retrofit-converter-simplexml": "com.squareup.retrofit2:converter-simplexml:${version["retrofitSdkVersion"]}",
    "retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}",
    "okhttp3" : "com.squareup.okhttp3:okhttp:${version["okhttpSdkVersion"]}",
    "okhttp3-logging-interceptor" : "com.squareup.okhttp3:logging-interceptor:${version["okhttpSdkVersion"]}",
    "mockwebserver" : "com.squareup.okhttp3:mockwebserver:${version["okhttpSdkVersion"]}",
    "glide" : "com.github.bumptech.glide:glide:${version["glideSdkVersion"]}",
    // (annotationProcessor)
    "glide-compiler" : "com.github.bumptech.glide:compiler:${version["glideSdkVersion"]}",
    "glide-loader-okhttp3" : "com.github.bumptech.glide:okhttp3-integration:${version["glideSdkVersion"]}",

    // view
    "butterknife" : "com.jakewharton:butterknife:${version["butterknifeSdkVersion"]}",
    "butterknife-compiler" : "com.jakewharton:butterknife-compiler:${version["butterknifeSdkVersion"]}",
    "brvah" : "com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.49-androidx",
    "psid" : "com.oushangfeng:PinnedSectionItemDecoration:1.3.2-androidx",
    "material-dialogs" : "com.afollestad.material-dialogs:core:2.8.1",
    "material-input" : "com.afollestad.material-dialogs:input:2.8.1",
    "material-files" : "com.afollestad.material-dialogs:files:2.8.1",
    "material-color" : "com.afollestad.material-dialogs:color:2.8.1",
    "material-datetime" : "com.afollestad.material-dialogs:datetime:2.8.1",
    "pickerview" : "com.contrarywind:Android-PickerView:4.1.8",
    "photoview" : "com.github.chrisbanes.photoview:library:2.0.0",
    "lottie" : "com.airbnb.android:lottie:3.0.1",
    "badge-view" : "q.rorbin:badgeview:1.1.3",

    // rx2
    "rxandroid2" : "io.reactivex.rxjava2:rxandroid:2.1.1",
    "rxjava2" : "io.reactivex.rxjava2:rxjava:2.2.16",
    // https://github.com/VictorAlbertos/RxCache
    "rxcache2" : "com.github.VictorAlbertos.RxCache:runtime:1.8.3-2.x",
    // https://github.com/tbruyelle/RxPermissions
    "rxpermissions2" : "com.github.tbruyelle:rxpermissions:0.10.2",

    // tools(implementation)
    "dagger2" : "com.google.dagger:dagger:${version["dagger2SdkVersion"]}",
    "dagger2-android" : "com.google.dagger:dagger-android:${version["dagger2SdkVersion"]}",
    "dagger2-android-support" : "com.google.dagger:dagger-android-support:${version["dagger2SdkVersion"]}",
    "eventbus" : "org.greenrobot:eventbus:3.1.1",
    "gson" : "com.google.code.gson:gson:2.8.5",
    // https://projectlombok.org/setup/android
    "lombok" : "org.projectlombok:lombok:1.18.8",
    "multidex" : "com.android.support:multidex:1.0.3",
    "arouter-api" : "com.alibaba:arouter-api:1.4.1",
    "arouter-compiler" : "com.alibaba:arouter-compiler:1.2.2",
    //(annotationProcessor)
    "dagger2-compiler" : "com.google.dagger:dagger-compiler:${version["dagger2SdkVersion"]}",
    "dagger2-android-processor" : "com.google.dagger:dagger-android-processor:${version["dagger2SdkVersion"]}",

    // test
    "junit" : "junit:junit:4.12",
    "androidJUnitRunner" : "androidx.test.runner.AndroidJUnitRunner",
    "runner" : "androidx.test:runner:1.1.1",
    "espresso-core" : "androidx.test.espresso:espresso-core:3.2.0",
    "espresso-contrib" : "androidx.test.espresso:espresso-contrib:3.2.0",
    "espresso-intents" : "androidx.test.espresso:espresso-intents:3.3.0",
    "canary-debug" : "com.squareup.leakcanary:leakcanary-android:${version["canarySdkVersion"]}",
    "canary-release" : "com.squareup.leakcanary:leakcanary-android-no-op:${version["canarySdkVersion"]}",
    "umeng-analytics" : "com.umeng.analytics:analytics:6.0.1",

    // util
    // https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/README-CN.md
    "utilcode" : "com.blankj:utilcode:1.23.7",

    // help
    "logger" : "com.orhanobut:logger:2.2.0",
    // https://www.pgyer.com/doc/view/new_sdk_android_guide
    "pgy" : "com.pgyersdk:sdk:3.0.3",
    // SDK 包
    // https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613
    // https://jcenter.bintray.com/com/tencent/bugly/crashreport/
    "crashreport" : "com.tencent.bugly:crashreport:3.1.0",
    // 升级 SDK 包
    // https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613
    // https://jcenter.bintray.com/com/tencent/bugly/crashreport_upgrade/
    "bugly-crash-upgrade" : "com.tencent.bugly:crashreport_upgrade:1.4.2",
    // NDK 动态库
    // https://bugly.qq.com/docs/release-notes/release-android-ndk/?v=20180709165613
    // https://jcenter.bintray.com/com/tencent/bugly/nativecrashreport/
    "bugly-ndk" : "com.tencent.bugly:nativecrashreport:3.7.1",

    ]
    }
  2. 在顶级的 build.gradle 文件底部,表明添加对 xxx.gradle 的使用
    1
    apply from: "xxx.gradle"
  3. 在 module 级别的 build.gradle 文件中,修改哪些固定写死的依赖版本
    1
    2
    3
    4
    // 之前固定的版本
    minSdkVersion 19
    // 修改通过 xxx.gradle 中定义的版本
    minSdkVersion rootProject.ext.android["minSdkVersion"]

参考

  1. Android Gradle 插件版本说明
  2. 配置构建
  3. 配置构建变体
]]>
- - - <p>在上一篇 Gradle 的文章中,已经对 Gradle 有了一定的认识,Gradle 在 Android 有着广泛的应用,用作 Android 包依赖管理,应用构建,测试,等一些列自动化,我们本篇就来了解下在 Android 领域 Gradle 的使用。其实 Android 项目结构和之前在第一篇 Gradle 项目结构基本相同,只是在 module 级别多了的 <span class="exturl" data-url="aHR0cDovL3Byb2d1YXJkLXJ1bGVzLnBybw==">proguard-rules.pro<i class="fa fa-external-link-alt"></i></span>。对于不管是 Android 项目或是 Spring 系列项目的子 module 都会有 build.gradle 文件</p> - - - - - - - - -
- - - Gradle(一)基础 - - https://incoder.org/2020/12/10/gradle1/ - 2020-12-10T10:11:46.000Z - 2024-08-11T12:14:45.515Z - - GitHub 上 Gralde 是这样描述,“Adaptable, fast automation for all”(让一切都能快速自动化
Gradle是一个构建工具,专注于构建自动化和对多语言开发的支持。对于在任何平台上的构建,测试,发布和部署,Gralde 提供了一种灵活的模型,可以支持从编译和打包代码到发布网站的整个生命周期。Gralde 旨在支持跨多种语言和平台的构建自动化,包括 Java,Scala,Android,Kotlin,C/C++ 和 Groovy,并于开发工具和包括 Eclipse,IntelliJ 和 Jenkins 的持续集成服务器紧密集成

  • Gradle official:https://gradle.org
  • Gradle docs:https://docs.gradle.org
  • Gradle plugins:https://plugins.gradle.org

Gradle 特点

  1. Gradle 基于 JVM 的构建工具
  2. 兼容支持 Maven,Ant 等
  3. 支持基于 Groovy 的构建脚本
  4. 编译构建执行效率更高
  5. 支持多种语言等
  6. 易于迁移

Gradle 安装配置

  • Gradle 官方:https://services.gradle.org/distributions/
  • Tencent 镜像:https://mirrors.cloud.tencent.com/gradle/

Tencent Gradle 镜像同步有一定的延迟,需要注意

下载

下载需要的版本即可,推荐最新版,这里以最新稳定版 6.7.1 为例,每个正式版本包含如下文件,我们选择 xxx-bin.zip(二进制版,只包含了二进制文件(可执行文件),没有文档和源代码) 或 xxx-all.zip(完整版,包含了各种二进制文件,源代码文件,和离线的文档)的文件即可,进行手动安装

1
2
3
4
5
6
7
8
9
10
gradle-6.7.1
├── gradle-6.7.1-wrapper.jar.sha256 # wrapper.jar hash 校验文件
├── gradle-6.7.1-docs.zip # gradle 文档压缩文件
├── gradle-6.7.1-docs.zip.sha256 # gradle 文档 hash 校验文件
├── gradle-6.7.1-src.zip # gradle 源码版,只包含了 Gradle 源代码,不能用来编译你的工程
├── gradle-6.7.1-src.zip.sha256 # gradle 源码版 hash 校验文件
├── gradle-6.7.1-bin.zip # gradle 核心压缩文件
├── gradle-6.7.1-bin.zip.sha256 # gradle 核心 hash 校验文件
├── gradle-6.7.1-all.zip # gradle 全部资源压缩文件
└── gradle-6.7.1-all.zip.sha256 # gradle 全部资源 hash 校验文件

当然如果你使用的 macOS 系统,且也已经安装了 homebrew 包管理工具,那么同样你也可以使用 brew 命令来安装 Gradle,那么你将不需要再去手动配置 Gradle 的环境,它的安装默认路径在 /usr/local/bin/gradle,安装完成后你就可以使用 gradle 的相关命令

1
2
3
4
5
6
# gradle 安装
brew install gradle
# gradle 升级
brew upgrade gradle
# 检查是否安装成功
gradle -v

配置

手动下载解压的文件进行安装,则需要配置 Gradle 的环境,这样方便我们在任何地方都可以调用 Gradle 的命令,对于 macOS 上手动安装配置 Gradle 环境的操作,可以参考 MacBook Pro 初始化 这篇文章 Gradle 配置

对于 Windows 系统,按照如下步骤进行添加环境变量,我这里 Windows 上为了和项目中 Gradle 版本有所区分,配置的是 6.7 版本


配置完成后,老规矩我们需要验证下我们的配置是否生效,在命令行中输入 gradle -v 命令,查看有 Gradle 相关的版本信息提示,我们的配置就已成功

GRADLE_HOME

GRADLE_HOME 这个环境变量,它主要是我们手动配置指定 GRADLE 使用的命令环境

GRADLE_USER_HOME

GRADLE_USER_HOME 指配置 Gradle 的安装下载的路径。默认 /Users/<PC NAME>/.gradle 路径,如果你在系统环境中设置了 GRADLE_USER_HOME 的环境变量,那么下载的路径就变成了你自定义设置的路径

Gradle 基础

刚刚在上面我们配置时,使用了 gradlew 命令,那这个又是啥呢,这里简单解释下,gradlew 是 gradle wrapper 的简写,对于 Gradle 构建的项目,用于解决 Gradle 安装,部署以及统一项目的 Gradle 的构建版本等一系列问题。

Gradle 有两个基本的概念:project 和 task,Gradle 里面的所有东西基于这两个概念

  • project:通常指一个项目
  • task:指构建过程中的任务

一次构建可以有 1 到 n 个 project,而每个 project 有 1 到 n 个 task

Gradle 项目

Android 项目工程一开始就默认使用 Gradle 来构建,在 Android 领域里使用花样也是比较多,更好体现了 Gradle 的灵活性,对于后端 Spring 系列项目,现在也是越来越多的开始使用 Gradle 来构建了,在 Spring Boot 2.3.0.M1 版本官方已开始在生产环境开始使用 Gradle 代替 Maven 进行构建,测试,发布项目。这从侧面也印证了 Gradle 对于复杂庞大的系统更加友好和高效

对于使用 Gradle 构建的 Android 项目也好,Java 项目也好,还是 SpringBoot 项目也罢,它们都有共同的特点。在结构上有下面的相同点

1
2
3
4
5
6
7
8
9
10
11
project
├── ……
├── .gradle/ # 项目使用 gradle 编译生成的临时文件存放位置
├── gradle/wrapper
│ │── gradle-wrapper.jar # gradlew 核心执行文件
│ └── gradle-wrapper.properties # gradle 运行环境配置文件
├── build.gradle # 项目依赖配置,脚本配置文件
├── gradlew # Linux or macOS 下可执行脚本
├── gradlew.bat # Windows 下可执行脚本
├── settings.gradle # 配置构建应用时应将哪些模块包含在内
└── ……

gradle-wrapper.properties

  • gradle-wrapper.jar 文件是项目中执行 gradlew 相关命令的具体实现,感兴趣的可以查看其中的具体源码实现
  • gradle-wrapper.properties 是 Gradle 项目版本管理的核心
    • distributionBase=GRADLE_USER_HOME:指定了 wrapper 保存下载的 Gradle 的主路径
    • distributionPath=wrapper/dists:指定了 wrapper 保存下载的 Gradle 的子路径
    • zipStoreBase=GRADLE_USER_HOME:指定了 wrapper 保存下载 gradle-6.7.1-bin.zip 文件的主路径
    • zipStorePath=wrapper/dists:指定了 gradle-6.7.1-bin.zip 文件的子路径
    • distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-bin.zip:gradle 文件下载的源地址

distributionBase 和 zipStoreBase 有两种取值

  • GRADLE_USER_HOME:默认使用方式,表示用户目录,默认路径 /Users/<PC NAME>/.gradle
  • PROJECT:表示工程的当前目录,不常用

对应 Gradle 的下载及解压目录这里还需要注意下

Gradle 的存放地址,比如:~/.gradle/wrapper/dists/gradle-6.7.1-bin/bwlcbys1h7rz3272sye1xwiv6 这里一个看起来无规则的文件夹,我们的 gradle 下载及解压必须放在这个文件夹内,而这个看似无规则的文件夹,实质是根据 distributionUrl 路径字符串计算 md5 值得来的

build.gradle 及 settings.gradle

对于build.gradlesettings.gradle 文件在 Android 应用和 SpringBoot 应用是不一样,因此关于他两介绍请移步 Gradle(二)AndroidGradle(三)SpringBoot 文章进行查看

Gradle 依赖

用于声明依赖关系的配置

配置名称角色是否可消费是否可分解描述
api声明API依赖项NN在这里,您可以声明依赖关系,这些依赖关系会在编译时和运行时以可传递方式导出到使用者
implementation声明实现依赖性NN在这里,您可以声明纯属内部的依赖关系,而不是要向使用方公开(在运行时仍向使用方公开)
compileOnly声明仅编译依赖项NN在这里可以声明在编译时需要的依赖项,而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项
compileOnlyApi声明仅编译API依赖项NN在这里,您可以声明模块和使用者在编译时需要的依赖项,而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项
runtimeOnly声明运行时依赖项NN在这里可以声明仅在运行时才需要的依赖关系,而在编译时则不需要
testImplementation测试依赖NN在这里声明用于编译测试的依赖项
testCompileOnly声明测试仅编译依赖项NN在这里声明仅在测试编译时需要的依赖项,而不应泄漏到运行时。这通常包括在运行时找到时会被阴影化的依赖项
testRuntimeOnly声明测试运行时依赖项NN在这里可以声明仅在测试运行时才需要的依赖项,而在测试编译时则不需要

核心需要掌握的是 apiimplementationcompileOnlyruntimeOnly 这4种依赖方式

对于你可能看到依赖方式,compile(api),provided(compileOnly),apk(runtimeOnly) 这些方式是比较旧的依赖方式,在 gradle plugin 3.0 开始已废弃,请使用新的依赖方式

本地依赖

本地依赖 module lib

通过这种方式依赖的弊端是每次都需要构建 module,但 module 比较多时构建非常耗时,建议控制 module 的依赖数量,避免构建耗时

1
2
// module 需要在项目根目录下的 settings.gradle 中通过 include 引入
implementation project(':libname')

本地二进制 lib 依赖

本地的 jar 或者 aar 需要放在 module 的 libs 文件夹下,通过这种方式依赖

依赖 jar
1
2
3
4
5
// 方式一:可以一次性依赖 libs 下的所有 jar
implementation fileTree(dir: 'libs', include: ['*.jar'])

// 方式二:可以指定依赖一个或几个 jar
implementation files('libs/xxxx1.jar', 'libs/xxxx2.jar')
依赖 aar
1
2
3
4
5
6
7
8
9
10
11
12
// 在 module 的 build.gradle 中添加目录指定
repositories {
flatDir {
dirs 'libs'
}
}

// 在 dependencies 中加入对 aar 的引入
// 方式一:可以一次性依赖 libs 下所有的 aar
implementation fileTree(dir: 'libs', include: ['*.aar'])
// 方式二:可以指定依赖某一个aar
implementation files(name: 'aar-lib-name', ext: 'aar')

远程二进制 lib 依赖

1
2
3
4
5
// 依赖明确的版本,标明 group、name 和 version
implementation group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.1.1'

// 常用的简写方式引用
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'

Gradle 命令

在项目中推荐使用 gradlew 命令来进行执行,这样本质是使用项目所依赖的 Gradle 版本进行执行。当然如果你本地配置了 Gradle 的环境变量,你可以将 gradlew 命令更改成 gradle 来执行

  • gradlew clean: 清除 build 文件夹
  • gradlew check: 执行 lint 检查
  • gradlew assemble: 编译并打包你的代码,但并不运行单元测试
  • gradlew build: 编译和测试你的代码,并生成一个包含所有类与资源的文件
  • gradlew dependencies: 查看所有依赖库
    • gradlew dependencies -configuration runtime: 查看运行时依赖库

注意:

  • Windows:在项目根目录,使用的是 gradlew
  • Linux or macOS:在项目的根目录,使用的是 ./gradlew

总结

  1. 对于 Gradle 我们不需要配置 GRADLE_USER_HOME 的环境,原因是项目中已对使用 Gradle 的版本做出了统一,我们仅需要根据自身的网络需要(如果从默认地址下载很慢,则需要配置好项目依赖镜像源)做出合适的配置。而如果你需要在任何地方使用 gradle 相关的命令,则配置 GRADLE_HOME 即可
  2. 依赖方式,我们选择 implementation 方式,这样可屏蔽掉不同应用之间因为引用了同一 lib 而不同版本造成的麻烦问题等

参考

  • gradle-wrapper.properties中各属性的含义
  • Dependency management in Gradle
  • The Java Library Plugin
  • The Distribution Plugin
]]>
- - - <p>GitHub 上 Gralde 是这样描述,“Adaptable, fast automation for all”(让一切都能<code>快速</code>的<code>自动化</code>)<br> -Gradle是一个构建工具,专注于构建自动化和对多语言开发的支持。对于在任何平台上的构建,测试,发布和部署,Gralde 提供了一种灵活的模型,可以支持从编译和打包代码到发布网站的整个生命周期。Gralde 旨在支持跨多种语言和平台的构建自动化,包括 Java,Scala,Android,Kotlin,C/C++ 和 Groovy,并于开发工具和包括 Eclipse,IntelliJ 和 Jenkins 的持续集成服务器紧密集成</p> - - - - - - - - -
- - - 迷宫如意琳琅图籍 - - https://incoder.org/2020/12/10/play-maze/ - 2020-12-10T09:44:46.000Z - 2024-08-11T12:14:45.515Z - -

故宫博物院出品,奥秘之家设计制作(曾推出线下实景地铁逃脱游戏,2018联合《唐人街探案 2》推出《侦探笔记》的互动解密游戏,以及配合电影开发Crimaster),到手快一年了还没有完全解锁线上的关卡,倒不是玩不下去,而是懒,刷 B 站多香,动啥脑子,哈哈哈。言归正传,本篇记录自己解锁线上关卡的步骤,持续更新

进度

  1. 初 ------------------ 100%
    • ✅ 梦入紫禁
    • ✅ 太和异象
    • ✅ 十八棵槐
  2. 壹 ------------------ 23%
    • 万寿盛筵
    • 殿前观礼
    • 礼乐度量
    • 一等画师
    • 腰牌买卖
    • 慈宁画样
  3. 贰 ------------------ 0%
    • 宫女禾心
    • 嘉祉初遇
    • 淑芳听戏
    • 戏里玄机
    • 上元之约
  4. 叁 ------------------ 0%
    • 结伴寻宝
    • 皇十五子
    • 档房探秘
    • 一路狂奔
    • 逢凶化吉
    • 五行八卦
    • 夜探御园
  5. 肆 ------------------ 0%
    • 琳琅宝藏
    • 花叶之谜
    • 宫中怪人
    • 图籍作者
    • 祸不单行
    • 五蕴皆空
  6. 伍 ------------------ 0%
    • 将破未破
    • 孤注一掷
  7. 隐 ------------------ 0%
    • 多年以后
  8. 众 ------------------ 0%
    • 众筹专属

梦入紫禁

太和异象

十八棵槐

万寿盛筵

殿前观礼

礼乐度量

一等画师

腰牌买卖

慈宁画样

宫女禾心

嘉祉初遇

淑芳听戏

戏里玄机

上元之约

结伴寻宝

皇十五子

档房探秘

一路狂奔

逢凶化吉

五行八卦

夜探御园

琳琅宝藏

花叶之谜

宫中怪人

图籍作者

祸不单行

五蕴皆空

将破未破

孤注一掷

多年以后

众筹专属

附录

  1. 奥秘之家官网:http://www.itaotuo.com/
  2. 《唐人街探案 2》之《侦探笔记》:https://www.zhihu.com/question/267341464
  3. 《唐人街探案 3》之《侦探笔记》:https://zhongchou.modian.com/item/90315.html
]]>
- - - <p><img src="https://res.cloudinary.com/incoder/image/upload/v1611486360/blog/G2.png" alt=""></p> - - - - - - - - -
- - - 搞定 m.2 接口 SSD - - https://incoder.org/2020/12/10/play-ssd/ - 2020-12-10T09:44:46.000Z - 2024-08-11T12:14:45.515Z - -

公司原装配置电脑磁盘性能太差,实在是不能满足我的日常骚操作,然后就自己买了一个 m.2 接口的 SSD 硬盘,毕竟电脑之前已经有系统了,而且也已经安装好了开发环境,如果现在在新的 SSD 上直接安装新的系统,那么需要将之前的开发环境再折腾一遍,实在是伤不起。那么有没有别的方式。你别说哦,还真的有,方法是用一些工具对现有系统进行 clone 到新的 SSD 磁盘上。这都很好办,比如:傲梅分区助手DiskGenius 都有系统迁移功能,可参考文章下方的参考地址,内有视频教程

注意:要设置好设置默认系统启动引导为新的磁盘

问题

一开始,我觉得这么简单的操作能有什么问题,迁移完系统,并设置好系统引导,然而我发现并不能按照预期使用 SSD 来启动,试了好几遍,调整了 BIOS 的启动选项,依旧不能解决。后来我将原系统的磁盘拆下来,只留 SSD 磁盘,开机就能按照预期启动了,正常后在把原系统磁盘再装回去,同时记得检查下系统引导,确保还依旧是使用 SSD 系统盘

参考

  1. SSD系统迁移工具:轻松迁移系统到SSD
  2. 使用分区助手快速将Windows系统迁移到新磁盘
]]>
- - - <p><img src="https://res.cloudinary.com/incoder/image/upload/v1611489458/blog/dell-ssd.jpg" alt=""></p> - - - - - - - - -
- - - 该死的 Base64,我惹你了? - - https://incoder.org/2020/11/27/damn-base64/ - 2020-11-27T14:43:46.000Z - 2024-08-11T12:14:45.511Z - - 在上一个项目中,由于客观原因,双方进行数据交换,用到对媒体文件(图片)进行了 Base64 编码处理,将编码后的数据存入了数据库,使用方再从数据库中取出数据进行解码恢复成图片,在实际处理中,这是最不推荐的做法。正确有效的做法是将资源文件存入到 OSS 系统中,数据库中记录文件的地址即可。但由于项目历史原因,无法使用 OSS 来处理,虽然说技术本质不难,编码存入,解码查看而已。但由于对方没有告知具体的编码方式,询问了好几次才最终给到对应的编码方式,浪费了大量的时间去沟通和试错,得不偿失

对于 Base64 ,开发者或多或少都有听过,严格意义上讲 Base64 不是加密方式,它只是一种编码方式,本篇文章就来详细的聊一聊 Base64 这个熟悉又陌生的朋友

什么是 Base64

Base64 的原理

常见的 Base64

解决实际问题

参考

  1. 密码学 | 庐山真面!你认为 Base64 是加密算法吗?
  2. 什么是Base64?
  3. Base64编码原理分析
  4. Base64编码
  5. Base64算法不一致可能会导致的坑
]]>
- - - <p>在上一个项目中,由于客观原因,双方进行数据交换,用到对媒体文件(图片)进行了 Base64 编码处理,将编码后的数据存入了数据库,使用方再从数据库中取出数据进行解码恢复成图片,在实际处理中,这是<strong>最不推荐</strong>的做法。正确有效的做法是将资源文件存入到 OSS 系统中,数据库中记录文件的地址即可。但由于项目历史原因,无法使用 OSS 来处理,虽然说技术本质不难,编码存入,解码查看而已。但由于对方没有告知具体的编码方式,询问了好几次才最终给到对应的编码方式,浪费了大量的时间去沟通和试错,得不偿失</p> - - - - - - - - -
- - - 开源协议,该如何选择 - - https://incoder.org/2020/11/25/open-license/ - 2020-11-25T22:30:10.000Z - 2024-08-11T12:14:45.515Z - - 现如今软件行业的发展完全离不开开源社区,很多优秀的软件应用、技术都能看到开源软件的影子,我们都是站在巨人的肩膀上。对于软件行业的从业者,能为开源项目贡献自己的力量,或是将自己对某一个细分领域所做的研究实践开源出来,这是一件非常值得骄傲的事情。而要参与一个大型的开源项目,你除了需要该项目涉及的核心技术知识外,还需要了解一定的开源项目运转方式等,对于如何参与开源项目,这里暂不做过多的介绍,有兴趣的可以移步 Gitee 发起的《开源指北》项目,该项目中详细介绍了如何参与开源项目。本篇文章也不啰嗦这一点,仅仅围绕开源协议,我们应该清楚的常识和注意的点

在软件开发中通常有两种情况我们需要考虑软件的开源协议或者使用协议

  1. 我们需要使用到业界的一些优秀的软件包来提高我们开发的效率,避免了重复早轮子,所选择的这些软件包我们不但考虑功能的同时,也要考虑软件包的授权协议
  2. 我们需要将自己的经验或者软件产品需要开源时,为了保护自己的权益,我们也需要选择一个合适的开源协议

我这里还是引用比较经典 阮一峰 文章中所绘制关于如何选择开源协议的图

图中已经很清楚的表示了如何去选择 LGPL, Mozilla, GPL, BSD, MIT, Apache 这 6 种协议

常用协议

这里我们通过表格的形式介绍下这 6 种协议,当然除了表中列出的这些协议之外还有很多协议,我们就挨个来简单对他们有一个了解和认识

其他协议

BY-NC-SA

你会发现每篇文章下面都有申明版权,这里使用的是 BY-NC-SA 4.0 的协议,他们的含义如下

  • :知识共享(CreativeCommons)
  • NC:非商业性使用(NonCommercial),您不得将本作品用于商业目的
  • SA:相同方式共享(ShareAlike),如果您再混合、转换或者基于本作品进行创作,您必须基于与原先许可协议相同的许可协议 分发您贡献的作品

使用此协议,您可以自由地

  1. 共享 — 在任何媒介以任何形式复制、发行本作品
  2. 演绎 — 修改、转换或以本作品为基础进行创作

只要你遵守许可协议条款,许可人就无法收回你的这些权利

选择

上面说了那么多,有些协议并没有展开来说可能并不适用你当前的所需要选择的协议,那么你可以根据实际情况去筛选,可通过 https://choosealicense.com, https://kaiyuanshe.cn/license-tool 这两个网站按照步骤去选择,最终确定协议即可

参考

  1. 开源许可证选择器
  2. Choose an open source license
  3. 博云违反 Apache 2.0 开源协议被要求整改,开源协议到底应该如何遵守?
  4. 开源协议是什么?有哪些?如何选择?
  5. 如何为你的代码选择一个开源协议
  6. 开源指北 Gitee
]]>
- - - <p>现如今软件行业的发展完全离不开开源社区,很多优秀的软件应用、技术都能看到开源软件的影子,我们都是站在巨人的肩膀上。对于软件行业的从业者,能为开源项目贡献自己的力量,或是将自己对某一个细分领域所做的研究实践开源出来,这是一件非常值得骄傲的事情。而要参与一个大型的开源项目,你除了需要该项目涉及的核心技术知识外,还需要了解一定的开源项目运转方式等,对于如何参与开源项目,这里暂不做过多的介绍,有兴趣的可以移步 Gitee 发起的《<span class="exturl" data-url="aHR0cHM6Ly9naXRlZS5jb20vZ2l0ZWUtY29tbXVuaXR5L29wZW5zb3VyY2UtZ3VpZGU=">开源指北<i class="fa fa-external-link-alt"></i></span>》项目,该项目中详细介绍了如何参与开源项目。本篇文章也不啰嗦这一点,仅仅围绕开源协议,我们应该清楚的常识和注意的点</p> - - - - - - - - -
- - - Hexo Blog 高级指南 - - https://incoder.org/2020/11/20/hexo-advanced/ - 2020-11-20T18:18:18.000Z - 2024-08-11T12:14:45.515Z - - NexTHexo 非常受欢迎的博客主题,方便简洁,但却不简单的功能,你可以在提供的强大功能基础上进行扩展或者自定义,来满足你的个性化需求。本篇文章主要是对应 NexT 提供的一些高级功能的使用,作为一个持续更新的文章吧,记录自己 SX 操作,当然也是我平时在使用 NexT 时遇到的一些问题的记录。好了废话不多说了,我们直接进入正题

博客升级

每次对于 NexT 的升级或多或少都会遇到些问题,这次也不例外,首先是对于不同版本的管理,由于一些历史原因有三个组织仓库分别对应不同的版本域,升级是需要注意下,本次我是从 7.8.0 版本升级到 8.0.x 版本,以后跟随官方,每月更新 NexT

npm 改成 yarn(可选)

yarn 的安装,请自行根据你的系统去安装,我这里 macOS 使用命令即可 brew install yarn

  1. 删除根目录的 package-lock.json,并在根目录执行 hexo clean && rm -rf node_modules/
  2. 根目录下执行 yarn install

更改 NexT 主题仓库

  1. 删除当前主题,在根目录下执行 rm -rf themes/
  2. 安装新的主题,
    • 方案一:在根目录下执行命令添加主题
    1
    git clone https://github.com/next-theme/hexo-theme-next themes/next
    • 方案二:通过 yarn 来管理主题
    1
    yarn add hexo-theme-next

修改配置

之前为了使主题更新不受影响,在项目的根目录 source/_data 路径下有一个 next.yml 文件来进行对 NexT 的自定义设置,那么在 8.0 版本开始,在项目根目录 _config.{theme}.yml 文件来代替之前在 source/_data 路径下的 next.yml 文件

问题

node --trace-warnings

异常信息

由于 NexT 需要 Hexo5.0+,在升级到 NexT 8.0.x 版本警告信息如下

1
2
3
4
5
6
7
(node:17336) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
(node:17336) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
原因分析

是由于 Hexo 项目嵌套依赖了 stylus 包,而对于 0.54.5 版本在 Node 14+ 版本存在问题,比如这里 hexo-renderer-stylus 包的依赖

1
2
3
4
5
6
7
……
│ │
├─┬ hexo-renderer-stylus@2.0.1
│ ├─┬ nib@1.1.2
│ │ └─┬ stylus@0.54.5
│ │ │
……
解决方法
  1. 可以降低你的 Node 版本到 12 版本
    1
    2
    3
    brew uninstall node
    brew install node@12
    brew link --overwrite --force node@12
  2. 推荐,更改替换 stylus 版本,在你的 package.json 文件中,添加如下配置
    1
    2
    3
    "resolutions": {
    "stylus": "^0.54.8"
    }
总结

🌀 pull-2538
🐞 issues-2534
🛠 solve-Accessing non-existent property

hexo-douban

之前用了 hexo-douban 插件来进行对 books 和 movies 进行管理,在升级到 Node 14+版本上,当前的插件也停止工作了,异常日志如下

1
2
3
INFO  0 books have been loaded in 1130 ms, because you are offline or your network is bad
INFO 0 movies have been loaded in 1329 ms, because you are offline or your network is bad
INFO 0 games have been loaded in 1004 ms, because you are offline or your network is bad

作者在🐞 issues-2534 做了回复,暂时没有替代方案,故在新版中,我停止了 hexo-douban 插件的使用,挖个坑,等自己有时间或者有人修复此问题再或者有替代插件后再重新启用

  1. 移除 hexo-douban 插件
    1
    2
    3
    4
    # yarn
    yarn remove hexo-douban
    # npm
    npm uninstall hexo-douban
  2. 移除 _config.yml 配置文件中,douban 的相关的配置
  3. 移除 _config.{theme}.yml 配置文件中,menu 配置的站点入口设置

博客评论

在 NexT version 8.1.0 版本,由于安全问题,Valine被移除暂时我并未迁移 Valine 的评论

博客已启用 utterances 评论支持,配置也比较简单,如下

1
2
3
4
5
6
7
utterances:
enable: true
repo: BladeCode/BladeCode.github.io # Github repository name
# Available values: pathname | url | title | og:title
issue_term: title
# Available values: github-light | github-dark | preferred-color-scheme | github-dark-orange | icy-dark | dark-blue | photon-dark | boxy-light
theme: github-light

文章加密

对于 NexT 的文章,有时需要进行加密访问,那么该怎么去处理呢,其实这一点在 NexT 的生态里已经有了这样的插件,我们可以直接在使用在我们的 NexT 里面,只需要简单的配置

1
2
3
4
# npm 
npm i hexo-blog-encrypt --save
# yarn
yarn add hexo-blog-encrypt

加密优先级:文章信息头 > 按标签加密

站点配置(_config.yml)

简单配置

1
2
3
# 文章密码访问 hexo-blog-encrypt
encrypt:
enable: true

更多配置

可以对一类(标签)来进行统一的密码设置

1
2
3
4
5
6
7
8
9
10
# 文章密码访问 hexo-blog-encrypt
encrypt:
abstract: 有东西被加密了, 请输入密码查看.
message: 您好, 这里需要密码.
tags:
- {name: tagNameA, password: 密码A}
- {name: tagNameB, password: 密码B}
template: <div id="hexo-blog-encrypt" data-wpm="{{hbeWrongPassMessage}}" data-whm="{{hbeWrongHashMessage}}"><div class="hbe-input-container"><input type="password" id="hbePass" placeholder="{{hbeMessage}}" /><label>{{hbeMessage}}</label><div class="bottom-line"></div></div><script id="hbeData" type="hbeData" data-hmacdigest="{{hbeHmacDigest}}">{{hbeEncryptedData}}</script></div>
wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.

单文章配置

在你需要加密的文章前面,根据需要添加对应的参数,这里仅是一个示例

1
2
3
4
5
6
7
8
9
10
11
---
title: Hello World
tags:
- 加密文章tag
date: 2020-11-20 18:18:18
password: helloworld
abstract: 该文章已加密, 请输入密码查看。
message: 该文章已加密, 请输入密码查看。
wrong_pass_message: 密码不正确,请重新输入!
wrong_hash_message: 文章不能被校验, 不过您还是能看看解密后的内容!
---

各参数说明

  • password:文章密码
  • abstract:文章摘要,会显示在博客的列表页
  • message:文章查看时,密码输入框上面的描述性文字
  • wrong_pass_message:校验失败提示
  • wrong_hash_message:hash 验证失败

多语言

对于多语言,根据自身需要添加,默认,修改博客项目根目录 _connfig.yml 文件 language 属性即可

  1. 对于单语言:language: xxx(具体语言可查看下方的官方说明)
  2. 对于多语言:
    • 语言添加
      1
      2
      3
      language:
      - zh-CN
      - en
    • 更改语言切换,_config.{theme}.yml 文件,language_switcher设置为 true
  3. 字段定义,如果一些字段的翻译不是你想要的,你可以自行修改
    • 在根目录的 source/_data 文件夹下,创建 languages.yml 文件
    • 在文件中,修改对应语言的字段
      1
      2
      3
      4
      5
      6
      7
      8
      9
      zh-CN:
      # items
      post:
      copyright:
      # the translation you perfer
      author: 本文博主
      en:
      menu:
      schedule: Calendar

多语言配置

GitHub Action

Hexo PWA

由于暂未支持 Hexo5.0+版本,先占坑

参考

  • 更新说明及常见问题
  • 将 Hexo 升级到 v5.0.0
  • 用 GitHub Actions 来自动部署 Hexo
  • Hexo博客部署PWA
  • 博客完美支持 PWA
  • 三步,让 Hexo 轻松支持 PWA
  • Pwabuilder
  • Hexo 相关问题和优化
]]>
- - - <p><span class="exturl" data-url="aHR0cHM6Ly90aGVtZS1uZXh0LmpzLm9yZw==">NexT<i class="fa fa-external-link-alt"></i></span> 是 <span class="exturl" data-url="aHR0cHM6Ly9oZXhvLmlvL3poLWNuL2luZGV4Lmh0bWw=">Hexo<i class="fa fa-external-link-alt"></i></span> 非常受欢迎的博客主题,方便简洁,但却不简单的功能,你可以在提供的强大功能基础上进行扩展或者自定义,来满足你的个性化需求。本篇文章主要是对应 NexT 提供的一些高级功能的使用,作为一个持续更新的文章吧,记录自己 SX 操作,当然也是我平时在使用 NexT 时遇到的一些问题的记录。好了废话不多说了,我们直接进入正题</p> - - - - - - - - -
- - - 2020 年秋季面试经历 - - https://incoder.org/2020/11/15/interview-2020/ - 2020-11-15T08:10:17.000Z - 2024-08-11T12:14:45.515Z - -
]]>
- - - 这是一篇加密博文,请输入密码后查看 - - - - - - - - - - -
- - - MacBook Pro 疑难杂症 - - https://incoder.org/2020/11/13/mac-question/ - 2020-11-13T02:04:46.000Z - 2024-08-11T12:14:45.515Z - - 这是一篇记录使用macOS系统时遇到的一些疑难杂症

macOS Big Sur

在 2020.11.13 正式推送了 macOS Big Sur version 11.0.1 版本,这一个版本是改动比较大的版本,这里关于它的新特性就不做介绍了,有兴趣的请查看官方网站介绍 Big Sur

Glance 失效

Glance 是一个快速预览增强,可以对一些文件进行快速预览,大大提高我们的日常效率,但该应用在 Big Sur 版本中不兼容,由于作者已入职 Apple,且对项目做了归档,不在维护,因此该问题依旧没有解决,可以使用一个付费的应用iPreView来满足当前需要

Glance 在 Big Sur 系统中失效

AirPods 异常

在 AirPods 使用过程中,发现有时候耳机并不能正常工作。通常情况下,我会断开与 macOS 的连接,重新连接,如果还是不能正常工作,在 macOS 的系统蓝牙设置里面,移除连接的耳机设备,将耳机放入 AirPods 盒子里面,先盖上盒子,然后再打开盒子,此时并按住 AirPods 盒子背后的按钮,直到前面呼吸灯变成白色,然后再 macOS 的蓝牙里面找到新的设备,并连接配对。同时也可参考官方指引步骤 连接并使用 AirPods 和 AirPods Pro

单耳工作

换一个连接设备,检查耳机是否正常,如果是正常,那说明耳机没有问题,问题就出在 macOS 声音管理上面,打开系统设置 -> 声音 -> 输出模式 ->设置为居中的平衡模式(既双耳工作)

airpods-settings

]]>
- - - <p>这是一篇记录使用macOS系统时遇到的一些疑难杂症</p> -<h2 id="macOS-Big-Sur"><a class="header-anchor" href="#macOS-Big-Sur"></a>macOS Big Sur</h2> -<p>在 2020.11.13 正式推送了 macOS Big Sur version 11.0.1 版本,这一个版本是改动比较大的版本,这里关于它的新特性就不做介绍了,有兴趣的请查看官方网站介绍 <span class="exturl" data-url="aHR0cHM6Ly93d3cuYXBwbGUuY29tLmNuL21hY29zL2JpZy1zdXI=">Big Sur<i class="fa fa-external-link-alt"></i></span></p> -<p><img src="https://res.cloudinary.com/incoder/image/upload/v1605885064/blog/macOS_Big_Sur.png" alt=""></p> - - - - - - - - - - -
- - - 微服务架构 - Alibaba 生态整合(一) - - https://incoder.org/2020/11/11/microservices-alibaba1/ - 2020-11-11T07:10:00.000Z - 2024-08-11T12:14:45.515Z - - 曾几何时,市面上对于微服务,分两个派系,一个派系以阿里为主的 Dubbo 生态体系,还有一派以 Spring Cloud 生态为主的体系,这两个系列的讨论也一直没有停息过。但现在 Spring Cloud Alibaba 的出现,提供了一整套构建分布式应用开发的微服务组件,由于这些组件是构建在原生的 Spring Cloud 之上,因此其服务治理方面的能力可认为是 Spring Cloud Plus, 不仅完全覆盖 Spring Cloud 原生特性,而且提供更为稳定和成熟的实现。那么从本系列就开始跟着我一起用阿里系的应用搭建分布式微服务应用,满足企业级的应用需要,而不是停留在 Dome 级别的应用框架使用。废话不多说,我们一起开始这一系列的实践

本篇文章主要讲一讲在构建分布式微服务应用时,经常遇到的问题以及对于同类型组件选择,以及在开发过程中相关问题的思考,对于在整个应用开发过程中,开发人员应该怎么去配合等等,那第一个问题是面对我们的业务场景该如何去做技术选型,我们先看 Spring 官方经典的微服务架构图

微服务的核心组件由:网关,服务注册发现,服务配置,熔断限流等组成

注意这里微服务主要以 Alibaba 系相关的开源组件为基础构建,并非是 Spring Cloud Alibaba 项目的照搬,而是基于企业实际业务需求的抽象整合,只为提高效率、总结编程套路以及提升编程思想

选型

  • 编程语言:Oracle JDK 8
  • 构建工具:Gradle
  • 网关路由:Spring Cloud Gateway
  • 服务通信:Dubbo
  • 消息管理:RockerMQ
  • 分布式事务:Seata
  • 注册中心及配置中心:Nacos
  • 限流,熔断,降级:Sentinel
  • 文档管理:SpringFox + Knife4j + Dubbo-Api-Docs
  • 部署发布:Docker + Nexus Repository OSS
  • 运维监控:Prometheus + Grafana

SpringCloud VS SpringCloud Alibaba

这里我汇总到表格中,方便查看比较

相关问题

JDK

OpenJDK

Java 最早由 SUN(Sun Microsystems,发起于美国斯坦福大学,SUN 是 Stanford University Network 的缩写)发明,2006 年 SUN 公司将 Java 开源,此时的 JDK 即为 OpenJDK

OpenJDK 是 Java SE 的开源实现,由 SUN 和 Java 社区提供支持,2009 年 Oracle 收购了 SUN 公司,自此 Java 的维护方之一的 SUN 也就变成了 Oracle

大多数 JDK 都是在 OpenJDK 的基础上编写实现的,比如 IBM J9,Azul Zulu,Azul Zing 和 Oracle JDK。几乎所有的 JDK 都派生自 OpenJDK,他们之间不同的是授权许可证。常见的 OpenJDK 发行商

发行商长期支持(TLS)许可证(license)TCK 测试未修改的上游构建提供商业支持
AdoptOpenJDKYesYesNoOptionalOptional(IBM)
Alibaba DragonwellYesYesYesNoNo
Amazon CorrettoYesYesYesNoOptional
(on AWS)
Azul ZuluYesYesYesNoOptional
BellSoft Liberica JDKYesYesYesNoOptional
IBM Java JDKYesNoYesNoYes
ojdkbuildYesYesNoYesNo
OpenLogic OpenJDKYesYesNoNoOptional
Oracle Java SEYesNoYesNoYes
Oracle OpenJDKNoYesYesYesNo
Red Hat OpenJDKYesYesYesNoYes
SAP SAPMachineYesYesYesNoNo

TLS:long-term support,长期支持(LTS)是一种产品生命周期管理策略,在该策略中,与标准版相比,计算机软件的稳定版本可以维持更长的时间。该术语通常保留给开源软件,它描述的软件版本比该软件的标准版本支持数月或数年的支持。
TCK:Technology Compatibility Kit,技术兼容性套件(TCK)是一套测试套件,至少名义上检查Java规范请求(JSR)的特定声称实施是否符合要求

OralceJDK

显而易见 OracleJDK 是在 Oracle 收购 SUN 公司之后,基于 OpenJDK 源码构建的 JDK 被命名了 OracleJDK,两则之间没有重大的技术差异

两者的区别

问题

有人会说了,这有啥好说的,我们在公司开发都是用 OracleJDK 的。曾经我也以为这两个区别不是很大,看公司的使用情况了,直到我使用了 CentOS 7 系统默认带的 OpenJDK 来编译 Gradle 项目,死活是编译不过,总是提醒我找不到 tools.jar 包。有图有真相

一开始,我把以为是环境配置的问题,但是经过一番折腾,卸载了自带的 OpenJDK,然后再用 yum install java 命令去安装 OpenJDK,发现并不是环境的问题,而是系统自带的这个 OpenJDK 是 JRE,所以并没有包含 tools.jar 文件。所以这个问题就是你系统 JDK 的问题了。建议卸载 JRE,重新安装 JDK

1
2
3
4
# 可以先查找 JDK,下面命令是我查找 java-1.8 的相关应用
yum search java-1.8 | grep -i --color JDK
# 也可以直接安装 JDK,比如我这里提供的 java-1.8.0-openjdk-devel.x86_64
yum install java-1.8.0-openjdk-devel.x86_64

Gradle or Maven

关于如何使用 Gradle 构建项目,以及使用 Gradle 配置符合企业敏捷开发需求,可查看我的 Gradle 系列的文章

jar 与 bootJar

之前在《SpringBoot(二) 启动分析JarLauncher》文章中进行对 SpringBoot 应用启动做了分析,提到了 jar 规范,做了简单的介绍,那么本篇在此基础上进一步的完善这个知识点

这里以 rc-microservices-alibaba 项目的 microservices-alibaba-gateway 模块的编译为例

jar

jar(Java Archive)可以看做是特殊文件压缩的一种,通常用于聚合大量的 Java 类文件,相关的元数据和资源文件到一个文件,以便分发 Java 平台应用软件或库。jar 文件是一种归档文件,以 ZIP 格式构建,以 .jar 为文件扩展名。包含一个可选的 META-INF 目录,可以通过命令行 jar 工具或使用 Java 平台中的 java.util.jar API 创建 jar 文件

可以看到,我们打包成 jar 的文件,仅仅是源码+资源文件,以及生成的 META_INF 文件

1
2
3
4
5
6
7
8
9
10
microservices-alibaba-gateway-1.0-SNAPSHOT
├── META-INF
├── org
│ └── incoder
│ └── gateway
│ ├── config
│ ├── exception
│ └── filter
├── static
└── templates

bootJar

看名字就知道,这是 SpringBoot 的专属 jar。为什么会有这种 jar,原因是在 SpringBoot 出现之前,我们的 jar 应用想要运行,需要将应用放入到 Tomcat 中。而 SpringBoot 的出现改变了这层关系,是 SpringBoot 在打包成 bootJar 时,会内置 Tomcat,我们可以直接运行启动 jar 应用,可能有人会说,这怎么改变了,不都还是运行在 Tomcat 上么。没错它确实依然运行在 Tomcat 上,但是他们的加载方式改变了

我们可以看到,打成 bootJar 的文件,除了 META-INF 相关文件,并且包含了 BOOT-INF 的 lib 路径下存放项目所使用的所有第三方的 jar 包 ,同时在打包的根目录,生成了 SpringBoot 的 loader 相关的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
microservices-alibaba-gateway-1.0-SNAPSHOT
├── BOOT-INF
│ ├── classes
│ │ ├── META-INF
│ │ ├── org
│ │ │ └── incoder
│ │ │ └── gateway
│ │ │ ├── config
│ │ │ ├── exception
│ │ │ └── filter
│ │ ├── static
│ │ └── templates
│ └── lib
├── META-INF
└── org
└── springframework
└── boot
└── loader
├── archive
├── data
├── jar
├── jarmode
└── util

Spring 生态

  1. Spring:一个一站式轻量级Java 开发框架,核心是控制反转(IOC)和面向切面(AOP),针对开发 Web 层,业务层,持久层等提供了多种配置解决方案,也是整个微服务开发的基石
  2. SpringMVC:是 Spring 基础之上的一个 MVC 框架,主要处理 Web 开发的路径映射和视图渲染,属于 Spring 框架中 Web 层开发的一部分(开发配置非常繁琐,复杂)
  3. SpringBoot:专注于服务方面的接口开发,和前端解耦,默认优于配置,一定程度上取消了 XML 配置,是一套快速开发的脚手架,能快速开发单个微服务
  4. SpringCloud:大部分功能组件基于 SpringBoot 去实现,提供了完整的微服务架构的技术生态,SpringCloud 专注于微服务的整合和管理

单工程 or 聚合工程

个人推荐单工程的方式,毕竟聚合工程最终会随着业务的发展推进,需要拆分为单项目开发管理,那还不如一开始就拆分

单工程

这里的单工程是指,每一个模块都是一个项目,由一个仓库进行管理,特点及要求如下

  1. 适合团队小组分工明确,开发人员多
  2. 适合项目迭代快
  3. 需要比较健全的基础设施,比如网关,公共基础工具包,消息管理,以及自动化部署相关服务设施

相关的构架过程可参考 Gradle(三)SpringBoot 单工程 文章

聚合工程

这里的聚合工程是指,将整个系统开发的所有模块以及公共模块都放在一个项目工程中,也就是用同一个仓库来进行管理,特点如下

  1. 适合项目初期,项目分工不是特别明确,开发人员少
  2. 项目需要集中管理

相关的构架过程可参考 Gradle(四)SpringBoot 聚合工程 文章

业务拆分

对于服务的拆分是没有统一的标准,除了通过实际的业务场景,团队能力,人员组织架构等多种因素综合考虑。都根据实际的需求进行调整,对于拆分主要从以下原则去思考

  1. 单一职责原则:保证每个服务只做好一件事,体现“高内聚,低耦合”,尽量减少对外界环境的依赖
  2. 服务依赖原则:避免服务间的循环依赖,在设计时就需要对服务进行分级,区分核心服务与非核心服务
  3. Two Pizza Team原则:让团队保持在2 个披萨就能让队员吃饱的小规模概念

参考

  • 传统行业转型微服务的挖坑与填坑
  • Java官方(Oracle/Sun)发布的JDK,和开源项目OpenJDK,里面包含的JVM是否相同
  • OpenJDK和Oracle JDK有什么区别和联系?
]]>
- - - <p>曾几何时,市面上对于微服务,分两个派系,一个派系以阿里为主的 Dubbo 生态体系,还有一派以 Spring Cloud 生态为主的体系,这两个系列的讨论也一直没有停息过。但现在 Spring Cloud Alibaba 的出现,提供了一整套构建分布式应用开发的微服务组件,由于这些组件是构建在原生的 Spring Cloud 之上,因此其服务治理方面的能力可认为是 Spring Cloud Plus, 不仅完全覆盖 Spring Cloud 原生特性,而且提供更为稳定和成熟的实现。那么从本系列就开始跟着我一起用阿里系的应用搭建分布式微服务应用,满足企业级的应用需要,而不是停留在 Dome 级别的应用框架使用。废话不多说,我们一起开始这一系列的实践</p> - - - - - - - - - - -
- -
diff --git a/categories/Agent/index.html b/categories/Agent/index.html deleted file mode 100644 index b5b5c0611..000000000 --- a/categories/Agent/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Agent | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Agent - 分类 -

-
- - -
- 2021 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Android/index.html b/categories/Android/index.html deleted file mode 100644 index d7ed4e045..000000000 --- a/categories/Android/index.html +++ /dev/null @@ -1,483 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Android | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Android - 分类 -

-
- - -
- 2019 -
- - - - - - - - - - -
- 2018 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Api/index.html b/categories/Api/index.html deleted file mode 100644 index 16a11ca55..000000000 --- a/categories/Api/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Api | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Api - 分类 -

-
- - -
- 2018 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/DataBase/MySQL/index.html b/categories/DataBase/MySQL/index.html deleted file mode 100644 index da9fae3ff..000000000 --- a/categories/DataBase/MySQL/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: MySQL | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

MySQL - 分类 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/DataBase/Realm/index.html b/categories/DataBase/Realm/index.html deleted file mode 100644 index 234251db8..000000000 --- a/categories/DataBase/Realm/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Realm | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Realm - 分类 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/DataBase/index.html b/categories/DataBase/index.html deleted file mode 100644 index 7b348946e..000000000 --- a/categories/DataBase/index.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: DataBase | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

DataBase - 分类 -

-
- - -
- 2019 -
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/DevTool/index.html b/categories/DevTool/index.html deleted file mode 100644 index 312be0100..000000000 --- a/categories/DevTool/index.html +++ /dev/null @@ -1,446 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: DevTool | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

DevTool - 分类 -

-
- - -
- 2020 -
- - - - -
- 2019 -
- - - - -
- 2018 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Docker/index.html b/categories/Docker/index.html deleted file mode 100644 index e2f7caaf5..000000000 --- a/categories/Docker/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Docker | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Docker - 分类 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Flowable/index.html b/categories/Flowable/index.html deleted file mode 100644 index 48bef0174..000000000 --- a/categories/Flowable/index.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Flowable | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Flowable - 分类 -

-
- - -
- 2020 -
- - - - -
- 2019 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Frame/index.html b/categories/Frame/index.html deleted file mode 100644 index ca8251f06..000000000 --- a/categories/Frame/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Frame | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Frame - 分类 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Git/index.html b/categories/Git/index.html deleted file mode 100644 index 82421e204..000000000 --- a/categories/Git/index.html +++ /dev/null @@ -1,463 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Git | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Git - 分类 -

-
- - -
- 2024 -
- - -
- 2018 -
- - - - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Google/index.html b/categories/Google/index.html deleted file mode 100644 index bfe83f08f..000000000 --- a/categories/Google/index.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Google | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Google - 分类 -

-
- - -
- 2019 -
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Gradle/index.html b/categories/Gradle/index.html deleted file mode 100644 index e564abce9..000000000 --- a/categories/Gradle/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Gradle | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Gradle - 分类 -

-
- - -
- 2020 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Hexo/index.html b/categories/Hexo/index.html deleted file mode 100644 index ff0dba999..000000000 --- a/categories/Hexo/index.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Hexo | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Hexo - 分类 -

-
- - -
- 2020 -
- - -
- 2018 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Hugo/index.html b/categories/Hugo/index.html deleted file mode 100644 index b76dea301..000000000 --- a/categories/Hugo/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Hugo | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Hugo - 分类 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/IDEA/index.html b/categories/IDEA/index.html deleted file mode 100644 index 29bab3a26..000000000 --- a/categories/IDEA/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: IDEA | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

IDEA - 分类 -

-
- - -
- 2019 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/JDK8/index.html b/categories/JDK8/index.html deleted file mode 100644 index bcef73613..000000000 --- a/categories/JDK8/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: JDK8 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

JDK8 - 分类 -

-
- - -
- 2020 -
- - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Java/index.html b/categories/Java/index.html deleted file mode 100644 index 8797552f8..000000000 --- a/categories/Java/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Java | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Java - 分类 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Linux/index.html b/categories/Linux/index.html deleted file mode 100644 index 80bd76614..000000000 --- a/categories/Linux/index.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Linux | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Linux - 分类 -

-
- - -
- 2021 -
- - -
- 2018 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/MQ/index.html b/categories/MQ/index.html deleted file mode 100644 index 5691a08b7..000000000 --- a/categories/MQ/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: MQ | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

MQ - 分类 -

-
- - -
- 2020 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Maven/index.html b/categories/Maven/index.html deleted file mode 100644 index e5a753089..000000000 --- a/categories/Maven/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Maven | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Maven - 分类 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Memory/Travel/index.html b/categories/Memory/Travel/index.html deleted file mode 100644 index f308257bd..000000000 --- a/categories/Memory/Travel/index.html +++ /dev/null @@ -1,423 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Travel | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Travel - 分类 -

-
- - -
- 2020 -
- - - - - - -
- 2018 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Memory/index.html b/categories/Memory/index.html deleted file mode 100644 index bf9d8048b..000000000 --- a/categories/Memory/index.html +++ /dev/null @@ -1,443 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Memory | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Memory - 分类 -

-
- - -
- 2020 -
- - - - - - -
- 2018 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Microservices/index.html b/categories/Microservices/index.html deleted file mode 100644 index 1c3e37a8c..000000000 --- a/categories/Microservices/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Microservices | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Microservices - 分类 -

-
- - -
- 2020 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Milepost/index.html b/categories/Milepost/index.html deleted file mode 100644 index 979dc86ce..000000000 --- a/categories/Milepost/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Milepost | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Milepost - 分类 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Movie/index.html b/categories/Movie/index.html deleted file mode 100644 index 4a3e21a9b..000000000 --- a/categories/Movie/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Movie | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Movie - 分类 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Netty/index.html b/categories/Netty/index.html deleted file mode 100644 index 2164efd5c..000000000 --- a/categories/Netty/index.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Netty | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Netty - 分类 -

-
- - -
- 2020 -
- - -
- 2019 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Network/index.html b/categories/Network/index.html deleted file mode 100644 index bb053688e..000000000 --- a/categories/Network/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Network | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Network - 分类 -

-
- - -
- 2018 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/OSS/index.html b/categories/OSS/index.html deleted file mode 100644 index d79b1fcb0..000000000 --- a/categories/OSS/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: OSS | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

OSS - 分类 -

-
- - -
- 2021 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Open-Source/index.html b/categories/Open-Source/index.html deleted file mode 100644 index 2c3d6b591..000000000 --- a/categories/Open-Source/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Open Source | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Open Source - 分类 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Play/index.html b/categories/Play/index.html deleted file mode 100644 index 7f8d21921..000000000 --- a/categories/Play/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Play | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Play - 分类 -

-
- - -
- 2020 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Resources/index.html b/categories/Resources/index.html deleted file mode 100644 index d127501c5..000000000 --- a/categories/Resources/index.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Resources | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Resources - 分类 -

-
- - -
- 2021 -
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/RxJava/index.html b/categories/RxJava/index.html deleted file mode 100644 index e2ef3990b..000000000 --- a/categories/RxJava/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: RxJava | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

RxJava - 分类 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Security/index.html b/categories/Security/index.html deleted file mode 100644 index 6d6a1dace..000000000 --- a/categories/Security/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Security | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Security - 分类 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/SpringBoot/index.html b/categories/SpringBoot/index.html deleted file mode 100644 index 599a7c886..000000000 --- a/categories/SpringBoot/index.html +++ /dev/null @@ -1,526 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: SpringBoot | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

SpringBoot - 分类 -

-
- - -
- 2020 -
- - - - - - - - - - - - - - -
- 2019 -
- - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/SpringBoot/page/2/index.html b/categories/SpringBoot/page/2/index.html deleted file mode 100644 index 0a454fa85..000000000 --- a/categories/SpringBoot/page/2/index.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: SpringBoot | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

SpringBoot - 分类 -

-
- - -
- 2019 -
- - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/SpringCloud/index.html b/categories/SpringCloud/index.html deleted file mode 100644 index a347f7aa8..000000000 --- a/categories/SpringCloud/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: SpringCloud | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

SpringCloud - 分类 -

-
- - -
- 2020 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Summary/index.html b/categories/Summary/index.html deleted file mode 100644 index cb6f49a49..000000000 --- a/categories/Summary/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Summary | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Summary - 分类 -

-
- - -
- 2020 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Sync/index.html b/categories/Sync/index.html deleted file mode 100644 index 13f3a75e0..000000000 --- a/categories/Sync/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Sync | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Sync - 分类 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Top/index.html b/categories/Top/index.html deleted file mode 100644 index 6aceeb778..000000000 --- a/categories/Top/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Top | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Top - 分类 -

-
- - -
- 2022 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Translation/index.html b/categories/Translation/index.html deleted file mode 100644 index e6971802d..000000000 --- a/categories/Translation/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Translation | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Translation - 分类 -

-
- - -
- 2019 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Util/index.html b/categories/Util/index.html deleted file mode 100644 index 901de0b81..000000000 --- a/categories/Util/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Util | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Util - 分类 -

-
- - -
- 2019 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Wechat/index.html b/categories/Wechat/index.html deleted file mode 100644 index 121c703be..000000000 --- a/categories/Wechat/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Wechat | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Wechat - 分类 -

-
- - -
- 2021 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/Windows/index.html b/categories/Windows/index.html deleted file mode 100644 index c1ae13c52..000000000 --- a/categories/Windows/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: Windows | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Windows - 分类 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/index.html b/categories/index.html deleted file mode 100644 index bf5633b42..000000000 --- a/categories/index.html +++ /dev/null @@ -1,336 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类 | 星海 - - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- - -
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/categories/macOS/index.html b/categories/macOS/index.html deleted file mode 100644 index ff580f949..000000000 --- a/categories/macOS/index.html +++ /dev/null @@ -1,423 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分类: macOS | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

macOS - 分类 -

-
- - -
- 2020 -
- - - - -
- 2018 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/css/hbe.style.css b/css/hbe.style.css deleted file mode 100644 index 060f1f83b..000000000 --- a/css/hbe.style.css +++ /dev/null @@ -1,749 +0,0 @@ -.hbe, -.hbe:after, -.hbe:before { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.hbe-container{ - margin: 0 auto; - overflow: hidden; -} -.hbe-content { - text-align: center; - font-size: 150%; - padding: 1em 0; -} - -.hbe-input { - position: relative; - z-index: 1; - display: inline-block; - margin: 1em; - width: 80%; - min-width: 200px; - vertical-align: top; -} - -.hbe-input-field { - line-height: normal; - font-size: 100%; - margin: 0; - position: relative; - display: block; - float: right; - padding: 0.8em; - width: 60%; - border: none; - border-radius: 0; - background: #f0f0f0; - color: #aaa; - font-weight: 400; - font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif; - -webkit-appearance: none; /* for box shadows to show on iOS */ -} - -.hbe-input-field:focus { - outline: none; -} - -.hbe-input-label { - display: inline-block; - float: right; - padding: 0 1em; - width: 40%; - color: #696969; - font-weight: bold; - font-size: 70.25%; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.hbe-input-label-content { - position: relative; - display: block; - padding: 1.6em 0; - width: 100%; -} - -.hbe-graphic { - position: absolute; - top: 0; - left: 0; - fill: none; -} - -/* hbe button in post page */ -.hbe-button { - width: 130px; - height: 40px; - background: linear-gradient(to bottom, #4eb5e5 0%,#389ed5 100%); /* W3C */ - border: none; - border-radius: 5px; - position: relative; - border-bottom: 4px solid #2b8bc6; - color: #fbfbfb; - font-weight: 600; - font-family: 'Open Sans', sans-serif; - text-shadow: 1px 1px 1px rgba(0,0,0,.4); - font-size: 15px; - text-align: left; - text-indent: 5px; - box-shadow: 0px 3px 0px 0px rgba(0,0,0,.2); - cursor: pointer; - - display: block; - margin: 0 auto; - margin-bottom: 20px; -} - -.hbe-button:active { - box-shadow: 0px 2px 0px 0px rgba(0,0,0,.2); - top: 1px; -} - -.hbe-button:after { - content: ""; - width: 0; - height: 0; - display: block; - border-top: 20px solid #187dbc; - border-bottom: 20px solid #187dbc; - border-left: 16px solid transparent; - border-right: 20px solid #187dbc; - position: absolute; - opacity: 0.6; - right: 0; - top: 0; - border-radius: 0 5px 5px 0; -} -/* hbe button in post page */ - -/* default theme {{{ */ -.hbe-input-default { - overflow: hidden; -} - -.hbe-input-field-default { - width: 100%; - background: transparent; - padding: 0.5em; - margin-bottom: 2em; - color: #f9f7f6; - z-index: 100; - opacity: 0; -} - -.hbe-input-label-default { - width: 100%; - position: absolute; - text-align: left; - padding: 0.5em 0; - pointer-events: none; - font-size: 1em; -} - -.hbe-input-label-default::before, -.hbe-input-label-default::after { - content: ''; - position: absolute; - width: 100%; - left: 0; -} - -.hbe-input-label-default::before { - height: 100%; - background: #666666; - top: 0; - -webkit-transform: translate3d(0, -100%, 0); - transform: translate3d(0, -100%, 0); - -webkit-transition: -webkit-transform 0.2s; - transition: transform 0.2s; -} - -.hbe-input-label-default::after { - height: 2px; - background: #666666; - top: 100%; - -webkit-transition: opacity 0.2s; - transition: opacity 0.2s; -} - -.hbe-input-label-content-default { - padding: 0; - -webkit-transform-origin: 0 0; - transform-origin: 0 0; - -webkit-transition: -webkit-transform 0.2s, color 0.2s; - transition: transform 0.2s, color 0.2s; -} - -.hbe-input-field-default:focus, -.hbe-input--filled .hbe-input-field-default { - opacity: 1; - -webkit-transition: opacity 0s 0.2s; - transition: opacity 0s 0.2s; -} - -.hbe-input-label-default::before, -.hbe-input-label-default::after, -.hbe-input-label-content-default, -.hbe-input-field-default:focus, -.hbe-input--filled .hbe-input-field-default { - -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); - transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); -} - -.hbe-input-field-default:focus + .hbe-input-label-default::before, -.hbe-input--filled .hbe-input-label-default::before { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} - -.hbe-input-field-default:focus + .hbe-input-label-default::after, -.hbe-input--filled .hbe-input-label-default::after { - opacity: 0; -} - -.hbe-input-field-default:focus + .hbe-input-label-default .hbe-input-label-content-default, -.hbe-input--filled .hbe-input-label-default .hbe-input-label-content-default { - color: #555555; - -webkit-transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1); - transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1); -} -/* default theme }}} */ - -/* up theme {{{ */ -.hbe-input-up { - overflow: hidden; - padding-top: 2em; -} - -.hbe-input-field-up { - width: 100%; - background: transparent; - opacity: 0; - padding: 0.35em; - z-index: 100; - color: #837482; -} - -.hbe-input-label-up { - width: 100%; - bottom: 0; - position: absolute; - pointer-events: none; - text-align: left; - color: #8E9191; - padding: 0 0.5em; -} - -.hbe-input-label-up::before { - content: ''; - position: absolute; - width: 100%; - height: 4em; - top: 100%; - left: 0; - background: #fff; - border-top: 4px solid #9B9F9F; - -webkit-transform: translate3d(0, -3px, 0); - transform: translate3d(0, -3px, 0); - -webkit-transition: -webkit-transform 0.4s; - transition: transform 0.4s; - -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); - transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); -} - -.hbe-input-label-content-up { - padding: 0.5em 0; - -webkit-transform-origin: 0% 100%; - transform-origin: 0% 100%; - -webkit-transition: -webkit-transform 0.4s, color 0.4s; - transition: transform 0.4s, color 0.4s; - -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); - transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); -} - -.hbe-input-field-up:focus, -.input--filled .hbe-input-field-up { - cursor: text; - opacity: 1; - -webkit-transition: opacity 0s 0.4s; - transition: opacity 0s 0.4s; -} - -.hbe-input-field-up:focus + .hbe-input-label-up::before, -.input--filled .hbe-input-label-up::before { - -webkit-transition-delay: 0.05s; - transition-delay: 0.05s; - -webkit-transform: translate3d(0, -3.3em, 0); - transform: translate3d(0, -3.3em, 0); -} - -.hbe-input-field-up:focus + .hbe-input-label-up .hbe-input-label-content-up, -.input--filled .hbe-input-label-content-up { - color: #6B6E6E; - -webkit-transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1); - transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1); -} -/* up theme }}} */ - -/* wave theme {{{ */ -.hbe-input-wave { - overflow: hidden; - padding-top: 1em; -} - -.hbe-input-field-wave { - padding: 0.5em 0em 0.25em; - width: 100%; - background: transparent; - color: #9da8b2; - font-size: 1.25em; -} - -.hbe-input-label-wave { - position: absolute; - top: 0.95em; - font-size: 0.85em; - left: 0; - display: block; - width: 100%; - text-align: left; - padding: 0em; - pointer-events: none; - -webkit-transform-origin: 0 0; - transform-origin: 0 0; - -webkit-transition: -webkit-transform 0.2s 0.15s, color 1s; - transition: transform 0.2s 0.15s, color 1s; - -webkit-transition-timing-function: ease-out; - transition-timing-function: ease-out; -} - -.hbe-graphic-wave { - stroke: #92989e; - pointer-events: none; - -webkit-transition: -webkit-transform 0.7s, stroke 0.7s; - transition: transform 0.7s, stroke 0.7s; - -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); - transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); -} - -.hbe-input-field-wave:focus + .hbe-input-label-wave, -.input--filled .hbe-input-label-wave { - color: #333; - -webkit-transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1); - transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1); -} - -.hbe-input-field-wave:focus ~ .hbe-graphic-wave, -.input--filled .graphic-wave { - stroke: #333; - -webkit-transform: translate3d(-66.6%, 0, 0); - transform: translate3d(-66.6%, 0, 0); -} -/* wave theme }}} */ - -/* flip theme {{{ */ -.hbe-input-field-flip { - width: 100%; - background-color: #d0d1d0; - border: 2px solid transparent; - -webkit-transition: background-color 0.25s, border-color 0.25s; - transition: background-color 0.25s, border-color 0.25s; -} - -.hbe-input-label-flip { - width: 100%; - text-align: left; - position: absolute; - bottom: 100%; - pointer-events: none; - overflow: hidden; - padding: 0 1.25em; - -webkit-transform: translate3d(0, 3em, 0); - transform: translate3d(0, 3em, 0); - -webkit-transition: -webkit-transform 0.25s; - transition: transform 0.25s ; - -webkit-transition-timing-function: ease-in-out; - transition-timing-function: ease-in-out; -} - -.hbe-input-label-content-flip { - color: #8B8C8B; - padding: 0.25em 0; - -webkit-transition: -webkit-transform 0.25s; - transition: transform 0.25s; - -webkit-transition-timing-function: ease-in-out; - transition-timing-function: ease-in-out; -} - -.hbe-input-label-content-flip::after { - content: attr(data-content); - position: absolute; - font-weight: 800; - bottom: 100%; - left: 0; - height: 100%; - width: 100%; - color: #666666; - padding: 0.25em 0; - letter-spacing: 1px; - font-size: 1em; -} - -.hbe-input-field-flip:focus + .hbe-input-label-flip, -.input--filled .hbe-input-label-flip { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} - -.hbe-input-field-flip:focus + .hbe-input-label-flip .hbe-input-label-content-flip, -.input--filled .hbe-input-label-content-flip { - -webkit-transform: translate3d(0, 100%, 0); - transform: translate3d(0, 100%, 0); -} - -.hbe-input-field-flip:focus + .hbe-input-field-flip, -.input--filled .hbe-input-field-flip { - background-color: transparent; - border-color: #666666; -} -/* flip theme }}} */ - -/* xray theme {{{ */ -.hbe-input-xray { - overflow: hidden; - padding-bottom: 2.5em; -} - -.hbe-input-field-xray { - padding: 0; - margin-top: 1.2em; - width: 100%; - background: transparent; - color: #84AF9B ; - font-size: 1.55em; -} - -.hbe-input-label-xray { - position: absolute; - top: 2em; - left: 0; - display: block; - width: 100%; - text-align: left; - padding: 0em; - letter-spacing: 1px; - color: #84AF9B ; - pointer-events: none; - -webkit-transform-origin: 0 0; - transform-origin: 0 0; - -webkit-transition: -webkit-transform 0.2s 0.1s, color 0.3s; - transition: transform 0.2s 0.1s, color 0.3s; - -webkit-transition-timing-function: ease-out; - transition-timing-function: ease-out; -} - -.hbe-graphic-xray { - stroke: #84AF9B ; - pointer-events: none; - stroke-width: 2px; - top: 1.25em; - bottom: 0px; - height: 3.275em; - -webkit-transition: -webkit-transform 0.7s, stroke 0.7s; - transition: transform 0.7s, stroke 0.7s; - -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); - transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); -} - -.hbe-input-field-xray:focus + .hbe-input-label-xray, -.input--filled .hbe-input-label-xray { - color: #84AF9B ; - -webkit-transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1); - transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1); -} - -.hbe-input-field-xray:focus ~ .hbe-graphic-xray, -.input--filled .graphic-xray { - stroke: #84AF9B ; - -webkit-transform: translate3d(-66.6%, 0, 0); - transform: translate3d(-66.6%, 0, 0); -} -/* xray theme }}} */ - -/* blink theme {{{ */ -.hbe-input-blink { - padding-top: 1em; -} - -.hbe-input-field-blink { - width: 100%; - padding: 0.8em 0.5em; - background: transparent; - border: 2px solid; - color: #8781bd; - -webkit-transition: border-color 0.25s; - transition: border-color 0.25s; -} - -.hbe-input-label-blink { - width: 100%; - position: absolute; - top: 0; - text-align: left; - overflow: hidden; - padding: 0; - pointer-events: none; - -webkit-transform: translate3d(0, 3em, 0); - transform: translate3d(0, 3em, 0); -} - -.hbe-input-label-content-blink { - padding: 0 1em; - font-weight: 400; - color: #b5b5b5; -} - -.hbe-input-label-content-blink::after { - content: attr(data-content); - position: absolute; - top: -200%; - left: 0; - color: #8781bd ; - font-weight: 800; -} - -.hbe-input-field-blink:focus, -.input--filled .hbe-input-field-blink { - border-color: #8781bd ; -} - -.hbe-input-field-blink:focus + .hbe-input-label-blink, -.input--filled .hbe-input-label-blink { - -webkit-animation: anim-blink-1 0.25s forwards; - animation: anim-blink-1 0.25s forwards; -} - -.hbe-input-field-blink:focus + .hbe-input-label-blink .hbe-input-label-content-blink, -.input--filled .hbe-input-label-content-blink { - -webkit-animation: anim-blink-2 0.25s forwards ease-in; - animation: anim-blink-2 0.25s forwards ease-in; -} - -@-webkit-keyframes anim-blink-1 { - 0%, 70% { - -webkit-transform: translate3d(0, 3em, 0); - transform: translate3d(0, 3em, 0); - } - 71%, 100% { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} - -@-webkit-keyframes anim-blink-2 { - 0% { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } - 70%, 71% { - -webkit-transform: translate3d(0, 125%, 0); - transform: translate3d(0, 125%, 0); - opacity: 0; - -webkit-animation-timing-function: ease-out; - } - 100% { - color: transparent; - -webkit-transform: translate3d(0, 200%, 0); - transform: translate3d(0, 200%, 0); - } -} - -@keyframes anim-blink-1 { - 0%, 70% { - -webkit-transform: translate3d(0, 3em, 0); - transform: translate3d(0, 3em, 0); - } - 71%, 100% { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} - -@keyframes anim-blink-2 { - 0% { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } - 70%, 71% { - -webkit-transform: translate3d(0, 125%, 0); - transform: translate3d(0, 125%, 0); - opacity: 0; - -webkit-animation-timing-function: ease-out; - } - 100% { - color: transparent; - -webkit-transform: translate3d(0, 200%, 0); - transform: translate3d(0, 200%, 0); - } -} -/* blink theme }}} */ - -/* surge theme {{{ */ -.hbe-input-surge { - overflow: hidden; - padding-bottom: 1em; -} - -.hbe-input-field-surge { - padding: 0.25em 0.5em; - margin-top: 1.25em; - width: 100%; - background: transparent; - color: #D0D0D0; - font-size: 1.55em; - opacity: 0; -} - -.hbe-input-label-surge { - width: 100%; - text-align: left; - position: absolute; - top: 1em; - pointer-events: none; - overflow: hidden; - padding: 0 0.25em; - -webkit-transform: translate3d(1em, 2.75em, 0); - transform: translate3d(1em, 2.75em, 0); - -webkit-transition: -webkit-transform 0.3s; - transition: transform 0.3s; -} - -.hbe-input-label-content-surge { - color: #A4A5A6; - padding: 0.4em 0 0.25em; - -webkit-transition: -webkit-transform 0.3s; - transition: transform 0.3s; -} - -.hbe-input-label-content-surge::after { - content: attr(data-content); - position: absolute; - font-weight: 800; - top: 100%; - left: 0; - height: 100%; - width: 100%; - color: #2C3E50; - padding: 0.25em 0; - letter-spacing: 1px; - font-size: 0.85em; -} - -.hbe-graphic-surge { - fill: #2C3E50; - pointer-events: none; - top: 1em; - bottom: 0px; - height: 4.5em; - z-index: -1; - -webkit-transition: -webkit-transform 0.7s, fill 0.7s; - transition: transform 0.7s, fill 0.7s; - -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); - transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); -} - -.hbe-input-field-surge:focus, -.input--filled .hbe-input-field-surge { - -webkit-transition: opacity 0s 0.35s; - transition: opacity 0s 0.35s; - opacity: 1; -} - -.hbe-input-field-surge:focus + .hbe-input-label-surge, -.input--filled .hbe-input-label-surge { - -webkit-transition-delay: 0.15s; - transition-delay: 0.15s; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} - -.hbe-input-field-surge:focus + .hbe-input-label-surge .hbe-input-label-content-surge, -.input--filled .hbe-input-label-content-surge { - -webkit-transition-delay: 0.15s; - transition-delay: 0.15s; - -webkit-transform: translate3d(0, -100%, 0); - transform: translate3d(0, -100%, 0); -} - -.hbe-input-field-surge:focus ~ .hbe-graphic-surge, -.input--filled .graphic-surge { - fill: #2C3E50; - -webkit-transform: translate3d(-66.6%, 0, 0); - transform: translate3d(-66.6%, 0, 0); -} -/* surge theme }}} */ - -/* shrink theme {{{ */ -.hbe-input-field-shrink { - width: 100%; - background: transparent; - padding: 0.5em 0; - margin-bottom: 2em; - color: #2C3E50; -} - -.hbe-input-label-shrink { - width: 100%; - position: absolute; - text-align: left; - font-size: 1em; - padding: 10px 0 5px; - pointer-events: none; -} - -.hbe-input-label-shrink::after { - content: ''; - position: absolute; - width: 100%; - height: 7px; - background: #B7C3AC; - left: 0; - top: 100%; - -webkit-transform-origin: 50% 100%; - transform-origin: 50% 100%; - -webkit-transition: -webkit-transform 0.3s, background-color 0.3s; - transition: transform 0.3s, background-color 0.3s; -} - -.hbe-input-label-content-shrink { - padding: 0; - -webkit-transform-origin: 0 0; - transform-origin: 0 0; - -webkit-transition: -webkit-transform 0.3s, color 0.3s; - transition: transform 0.3s, color 0.3s; -} - -.hbe-input-field-shrink:focus + .hbe-input-label-shrink::after, -.input--filled .hbe-input-label-shrink::after { - background: #84AF9B; - -webkit-transform: scale3d(1, 0.25, 1); - transform: scale3d(1, 0.25, 1); -} - -.hbe-input-field-shrink:focus + .hbe-input-label-shrink .hbe-input-label-content-shrink, -.input--filled .hbe-input-label-shrink .hbe-input-label-content-shrink { - color: #84AF9B; - -webkit-transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1); - transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1); -} -/* shrink theme }}} */ diff --git a/css/main.css b/css/main.css deleted file mode 100644 index c774684a3..000000000 --- a/css/main.css +++ /dev/null @@ -1,3272 +0,0 @@ -:root { - --body-bg-color: #fff; - --content-bg-color: #f5f5f5; - --card-bg-color: #f5f5f5; - --text-color: #555; - --blockquote-color: #666; - --link-color: #555; - --link-hover-color: #222; - --brand-color: #222; - --brand-hover-color: #222; - --table-row-odd-bg-color: #f9f9f9; - --table-row-hover-bg-color: #f5f5f5; - --menu-item-bg-color: #ddd; - --theme-color: #222; - --btn-default-bg: transparent; - --btn-default-color: var(--link-color); - --btn-default-border-color: var(--link-color); - --btn-default-hover-bg: transparent; - --btn-default-hover-color: var(--link-hover-color); - --btn-default-hover-border-color: var(--link-hover-color); - --highlight-background: #f8f8f2; - --highlight-foreground: #232629; - --highlight-gutter-background: #e2e3dd; - --highlight-gutter-foreground: #383b3d; - color-scheme: light; -} -@media (prefers-color-scheme: dark) { - :root { - --body-bg-color: #282828; - --content-bg-color: #333; - --card-bg-color: #555; - --text-color: #ccc; - --blockquote-color: #bbb; - --link-color: #ccc; - --link-hover-color: #eee; - --brand-color: #ddd; - --brand-hover-color: #ddd; - --table-row-odd-bg-color: #282828; - --table-row-hover-bg-color: #363636; - --menu-item-bg-color: #555; - --theme-color: #222; - --btn-default-bg: #222; - --btn-default-color: #ccc; - --btn-default-border-color: #555; - --btn-default-hover-bg: #666; - --btn-default-hover-color: #ccc; - --btn-default-hover-border-color: #666; - --highlight-background: #232629; - --highlight-foreground: #f8f8f2; - --highlight-gutter-background: #383b3d; - --highlight-gutter-foreground: #e2e3dd; - color-scheme: dark; - } - img { - opacity: 0.75; - } - img:hover { - opacity: 0.9; - } - iframe { - color-scheme: light; - } -} -html { - line-height: 1.15; /* 1 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} -body { - margin: 0; -} -main { - display: block; -} -h1 { - font-size: 2em; - margin: 0.67em 0; -} -hr { - box-sizing: content-box; /* 1 */ - height: 0; /* 1 */ - overflow: visible; /* 2 */ -} -pre { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} -a { - background: transparent; -} -abbr[title] { - border-bottom: none; /* 1 */ - text-decoration: underline; /* 2 */ - text-decoration: underline dotted; /* 2 */ -} -b, -strong { - font-weight: bolder; -} -code, -kbd, -samp { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} -small { - font-size: 80%; -} -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} -sub { - bottom: -0.25em; -} -sup { - top: -0.5em; -} -img { - border-style: none; -} -button, -input, -optgroup, -select, -textarea { - font-family: inherit; /* 1 */ - font-size: 100%; /* 1 */ - line-height: 1.15; /* 1 */ - margin: 0; /* 2 */ -} -button, -input { -/* 1 */ - overflow: visible; -} -button, -select { -/* 1 */ - text-transform: none; -} -button, -[type='button'], -[type='reset'], -[type='submit'] { - -webkit-appearance: button; -} -button::-moz-focus-inner, -[type='button']::-moz-focus-inner, -[type='reset']::-moz-focus-inner, -[type='submit']::-moz-focus-inner { - border-style: none; - padding: 0; -} -button:-moz-focusring, -[type='button']:-moz-focusring, -[type='reset']:-moz-focusring, -[type='submit']:-moz-focusring { - outline: 1px dotted ButtonText; -} -fieldset { - padding: 0.35em 0.75em 0.625em; -} -legend { - box-sizing: border-box; /* 1 */ - color: inherit; /* 2 */ - display: table; /* 1 */ - max-width: 100%; /* 1 */ - padding: 0; /* 3 */ - white-space: normal; /* 1 */ -} -progress { - vertical-align: baseline; -} -textarea { - overflow: auto; -} -[type='checkbox'], -[type='radio'] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} -[type='number']::-webkit-inner-spin-button, -[type='number']::-webkit-outer-spin-button { - height: auto; -} -[type='search'] { - outline-offset: -2px; /* 2 */ - -webkit-appearance: textfield; /* 1 */ -} -[type='search']::-webkit-search-decoration { - -webkit-appearance: none; -} -::-webkit-file-upload-button { - font: inherit; /* 2 */ - -webkit-appearance: button; /* 1 */ -} -details { - display: block; -} -summary { - display: list-item; -} -template { - display: none; -} -[hidden] { - display: none; -} -::selection { - background: #262a30; - color: #eee; -} -html, -body { - height: 100%; -} -body { - background: var(--body-bg-color); - box-sizing: border-box; - color: var(--text-color); - font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; - font-size: 1em; - line-height: 2; - min-height: 100%; - position: relative; - transition: padding 0.2s ease-in-out; -} -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; - font-weight: bold; - line-height: 1.5; - margin: 30px 0 15px; -} -h1 { - font-size: 1.5em; -} -h2 { - font-size: 1.375em; -} -h3 { - font-size: 1.25em; -} -h4 { - font-size: 1.125em; -} -h5 { - font-size: 1em; -} -h6 { - font-size: 0.875em; -} -p { - margin: 0 0 20px; -} -a { - border-bottom: 1px solid #ccc; - color: var(--link-color); - cursor: pointer; - outline: 0; - text-decoration: none; - overflow-wrap: break-word; -} -a:hover { - border-bottom-color: var(--link-hover-color); - color: var(--link-hover-color); -} -iframe, -img, -video, -embed { - display: block; - margin-left: auto; - margin-right: auto; - max-width: 100%; -} -hr { - background-image: repeating-linear-gradient(-45deg, #ddd, #ddd 4px, transparent 4px, transparent 8px); - border: 0; - height: 3px; - margin: 40px 0; -} -blockquote { - border-left: 4px solid #ddd; - color: var(--blockquote-color); - margin: 0; - padding: 0 15px; -} -blockquote cite::before { - content: '-'; - padding: 0 5px; -} -dt { - font-weight: bold; -} -dd { - margin: 0; - padding: 0; -} -.table-container { - overflow: auto; -} -table { - border-collapse: collapse; - border-spacing: 0; - font-size: 0.875em; - margin: 0 0 20px; - width: 100%; -} -tbody tr:nth-of-type(odd) { - background: var(--table-row-odd-bg-color); -} -tbody tr:hover { - background: var(--table-row-hover-bg-color); -} -caption, -th, -td { - padding: 8px; -} -th, -td { - border: 1px solid #ddd; - border-bottom: 3px solid #ddd; -} -th { - font-weight: 700; - padding-bottom: 10px; -} -td { - border-bottom-width: 1px; -} -.btn { - background: var(--btn-default-bg); - border: 2px solid var(--btn-default-border-color); - border-radius: 0; - color: var(--btn-default-color); - display: inline-block; - font-size: 0.875em; - line-height: 2; - padding: 0 20px; - transition: background-color 0.2s ease-in-out; -} -.btn:hover { - background: var(--btn-default-hover-bg); - border-color: var(--btn-default-hover-border-color); - color: var(--btn-default-hover-color); -} -.btn + .btn { - margin: 0 0 8px 8px; -} -.btn .fa-fw { - text-align: left; - width: 1.285714285714286em; -} -.toggle { - line-height: 0; -} -.toggle .toggle-line { - background: #fff; - display: block; - height: 2px; - left: 0; - position: relative; - top: 0; - transition: all 0.4s; - width: 100%; -} -.toggle .toggle-line:first-child { - margin-top: 1px; -} -.toggle .toggle-line:not(:first-child) { - margin-top: 4px; -} -.toggle.toggle-arrow :first-child { - top: 2px; - transform: rotate(-45deg); - width: 50%; -} -.toggle.toggle-arrow :last-child { - top: -2px; - transform: rotate(45deg); - width: 50%; -} -.toggle.toggle-close :nth-child(2) { - opacity: 0; -} -.toggle.toggle-close :first-child { - top: 6px; - transform: rotate(-45deg); -} -.toggle.toggle-close :last-child { - top: -6px; - transform: rotate(45deg); -} -pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em -} -code.hljs { - padding: 3px 5px -} -/*! - Theme: Humanoid light - Author: Thomas (tasmo) Friese - License: ~ MIT (or more permissive) [via base16-schemes-source] - Maintainer: @highlightjs/core-team - Version: 2021.09.0 -*/ -/* - WARNING: DO NOT EDIT THIS FILE DIRECTLY. - - This theme file was auto-generated from the Base16 scheme humanoid-light - by the Highlight.js Base16 template builder. - - - https://github.com/highlightjs/base16-highlightjs -*/ -/* -base00 #f8f8f2 Default Background -base01 #efefe9 Lighter Background (Used for status bars, line number and folding marks) -base02 #deded8 Selection Background -base03 #c0c0bd Comments, Invisibles, Line Highlighting -base04 #60615d Dark Foreground (Used for status bars) -base05 #232629 Default Foreground, Caret, Delimiters, Operators -base06 #2f3337 Light Foreground (Not often used) -base07 #070708 Light Background (Not often used) -base08 #b0151a Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted -base09 #ff3d00 Integers, Boolean, Constants, XML Attributes, Markup Link Url -base0A #ffb627 Classes, Markup Bold, Search Text Background -base0B #388e3c Strings, Inherited Class, Markup Code, Diff Inserted -base0C #008e8e Support, Regular Expressions, Escape Characters, Markup Quotes -base0D #0082c9 Functions, Methods, Attribute IDs, Headings -base0E #700f98 Keywords, Storage, Selector, Markup Italic, Diff Changed -base0F #b27701 Deprecated, Opening/Closing Embedded Language Tags, e.g. -*/ -pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em -} -code.hljs { - padding: 3px 5px -} -.hljs { - color: #232629; - background: #f8f8f2 -} -.hljs::selection, -.hljs ::selection { - background-color: #deded8; - color: #232629 -} -/* purposely do not highlight these things */ -.hljs-formula, -.hljs-params, -.hljs-property { - -} -/* base03 - #c0c0bd - Comments, Invisibles, Line Highlighting */ -.hljs-comment { - color: #c0c0bd -} -/* base04 - #60615d - Dark Foreground (Used for status bars) */ -.hljs-tag { - color: #60615d -} -/* base05 - #232629 - Default Foreground, Caret, Delimiters, Operators */ -.hljs-subst, -.hljs-punctuation, -.hljs-operator { - color: #232629 -} -.hljs-operator { - opacity: 0.7 -} -/* base08 - Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted */ -.hljs-bullet, -.hljs-variable, -.hljs-template-variable, -.hljs-selector-tag, -.hljs-name, -.hljs-deletion { - color: #b0151a -} -/* base09 - Integers, Boolean, Constants, XML Attributes, Markup Link Url */ -.hljs-symbol, -.hljs-number, -.hljs-link, -.hljs-attr, -.hljs-variable.constant_, -.hljs-literal { - color: #ff3d00 -} -/* base0A - Classes, Markup Bold, Search Text Background */ -.hljs-title, -.hljs-class .hljs-title, -.hljs-title.class_ { - color: #ffb627 -} -.hljs-strong { - font-weight: bold; - color: #ffb627 -} -/* base0B - Strings, Inherited Class, Markup Code, Diff Inserted */ -.hljs-code, -.hljs-addition, -.hljs-title.class_.inherited__, -.hljs-string { - color: #388e3c -} -/* base0C - Support, Regular Expressions, Escape Characters, Markup Quotes */ -/* guessing */ -.hljs-built_in, -.hljs-doctag, -.hljs-quote, -.hljs-keyword.hljs-atrule, -.hljs-regexp { - color: #008e8e -} -/* base0D - Functions, Methods, Attribute IDs, Headings */ -.hljs-function .hljs-title, -.hljs-attribute, -.ruby .hljs-property, -.hljs-title.function_, -.hljs-section { - color: #0082c9 -} -/* base0E - Keywords, Storage, Selector, Markup Italic, Diff Changed */ -/* .hljs-selector-id, */ -/* .hljs-selector-class, */ -/* .hljs-selector-attr, */ -/* .hljs-selector-pseudo, */ -.hljs-type, -.hljs-template-tag, -.diff .hljs-meta, -.hljs-keyword { - color: #700f98 -} -.hljs-emphasis { - color: #700f98; - font-style: italic -} -/* base0F - Deprecated, Opening/Closing Embedded Language Tags, e.g. */ -/* - prevent top level .keyword and .string scopes - from leaking into meta by accident -*/ -.hljs-meta, -.hljs-meta .hljs-keyword, -.hljs-meta .hljs-string { - color: #b27701 -} -/* for v10 compatible themes */ -.hljs-meta .hljs-keyword, -.hljs-meta-keyword { - font-weight: bold -} -@media (prefers-color-scheme: dark) { -pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em -} -code.hljs { - padding: 3px 5px -} -/*! - Theme: Humanoid dark - Author: Thomas (tasmo) Friese - License: ~ MIT (or more permissive) [via base16-schemes-source] - Maintainer: @highlightjs/core-team - Version: 2021.09.0 -*/ -/* - WARNING: DO NOT EDIT THIS FILE DIRECTLY. - - This theme file was auto-generated from the Base16 scheme humanoid-dark - by the Highlight.js Base16 template builder. - - - https://github.com/highlightjs/base16-highlightjs -*/ -/* -base00 #232629 Default Background -base01 #333b3d Lighter Background (Used for status bars, line number and folding marks) -base02 #484e54 Selection Background -base03 #60615d Comments, Invisibles, Line Highlighting -base04 #c0c0bd Dark Foreground (Used for status bars) -base05 #f8f8f2 Default Foreground, Caret, Delimiters, Operators -base06 #fcfcf6 Light Foreground (Not often used) -base07 #fcfcfc Light Background (Not often used) -base08 #f11235 Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted -base09 #ff9505 Integers, Boolean, Constants, XML Attributes, Markup Link Url -base0A #ffb627 Classes, Markup Bold, Search Text Background -base0B #02d849 Strings, Inherited Class, Markup Code, Diff Inserted -base0C #0dd9d6 Support, Regular Expressions, Escape Characters, Markup Quotes -base0D #00a6fb Functions, Methods, Attribute IDs, Headings -base0E #f15ee3 Keywords, Storage, Selector, Markup Italic, Diff Changed -base0F #b27701 Deprecated, Opening/Closing Embedded Language Tags, e.g. -*/ -pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em -} -code.hljs { - padding: 3px 5px -} -.hljs { - color: #f8f8f2; - background: #232629 -} -.hljs::selection, -.hljs ::selection { - background-color: #484e54; - color: #f8f8f2 -} -/* purposely do not highlight these things */ -.hljs-formula, -.hljs-params, -.hljs-property { - -} -/* base03 - #60615d - Comments, Invisibles, Line Highlighting */ -.hljs-comment { - color: #60615d -} -/* base04 - #c0c0bd - Dark Foreground (Used for status bars) */ -.hljs-tag { - color: #c0c0bd -} -/* base05 - #f8f8f2 - Default Foreground, Caret, Delimiters, Operators */ -.hljs-subst, -.hljs-punctuation, -.hljs-operator { - color: #f8f8f2 -} -.hljs-operator { - opacity: 0.7 -} -/* base08 - Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted */ -.hljs-bullet, -.hljs-variable, -.hljs-template-variable, -.hljs-selector-tag, -.hljs-name, -.hljs-deletion { - color: #f11235 -} -/* base09 - Integers, Boolean, Constants, XML Attributes, Markup Link Url */ -.hljs-symbol, -.hljs-number, -.hljs-link, -.hljs-attr, -.hljs-variable.constant_, -.hljs-literal { - color: #ff9505 -} -/* base0A - Classes, Markup Bold, Search Text Background */ -.hljs-title, -.hljs-class .hljs-title, -.hljs-title.class_ { - color: #ffb627 -} -.hljs-strong { - font-weight: bold; - color: #ffb627 -} -/* base0B - Strings, Inherited Class, Markup Code, Diff Inserted */ -.hljs-code, -.hljs-addition, -.hljs-title.class_.inherited__, -.hljs-string { - color: #02d849 -} -/* base0C - Support, Regular Expressions, Escape Characters, Markup Quotes */ -/* guessing */ -.hljs-built_in, -.hljs-doctag, -.hljs-quote, -.hljs-keyword.hljs-atrule, -.hljs-regexp { - color: #0dd9d6 -} -/* base0D - Functions, Methods, Attribute IDs, Headings */ -.hljs-function .hljs-title, -.hljs-attribute, -.ruby .hljs-property, -.hljs-title.function_, -.hljs-section { - color: #00a6fb -} -/* base0E - Keywords, Storage, Selector, Markup Italic, Diff Changed */ -/* .hljs-selector-id, */ -/* .hljs-selector-class, */ -/* .hljs-selector-attr, */ -/* .hljs-selector-pseudo, */ -.hljs-type, -.hljs-template-tag, -.diff .hljs-meta, -.hljs-keyword { - color: #f15ee3 -} -.hljs-emphasis { - color: #f15ee3; - font-style: italic -} -/* base0F - Deprecated, Opening/Closing Embedded Language Tags, e.g. */ -/* - prevent top level .keyword and .string scopes - from leaking into meta by accident -*/ -.hljs-meta, -.hljs-meta .hljs-keyword, -.hljs-meta .hljs-string { - color: #b27701 -} -/* for v10 compatible themes */ -.hljs-meta .hljs-keyword, -.hljs-meta-keyword { - font-weight: bold -} -} -.highlight:hover .copy-btn, -.code-container:hover .copy-btn { - opacity: 1; -} -.code-container { - position: relative; -} -.copy-btn { - color: #333; - cursor: pointer; - line-height: 1.6; - opacity: 0; - padding: 2px 6px; - position: absolute; - transition: opacity 0.2s ease-in-out; - color: var(--highlight-foreground); - font-size: 14px; - right: 0; - top: 2px; -} -figure.highlight { - border-radius: 5px; - box-shadow: 0 10px 30px 0 rgba(0,0,0,0.4); - padding-top: 30px; -} -figure.highlight .table-container { - border-radius: 0 0 5px 5px; -} -figure.highlight::before { - background: #fc625d; - box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b; - left: 12px; - margin-top: -20px; - position: absolute; - border-radius: 50%; - content: ' '; - height: 12px; - width: 12px; -} -.expand-btn { - bottom: 0; - color: var(--highlight-foreground); - cursor: pointer; - display: none; - left: 0; - right: 0; - position: absolute; - text-align: center; -} -.fold-cover { - background-image: linear-gradient(to top, var(--highlight-background) 0, rgba(0,0,0,0) 100%); - bottom: 0; - display: none; - height: 50px; - left: 0; - right: 0; - position: absolute; -} -.highlight-fold { - max-height: 300px; - overflow-y: hidden !important; -} -.highlight-fold .expand-btn, -.highlight-fold .fold-cover { - display: block; -} -code, -kbd, -figure.highlight, -pre { - background: var(--highlight-background); - color: var(--highlight-foreground); -} -figure.highlight, -pre { - line-height: 1.6; - margin: 0 auto 20px; -} -figure.highlight figcaption, -pre .caption, -pre figcaption { - background: var(--highlight-gutter-background); - color: var(--highlight-foreground); - display: flow-root; - font-size: 0.875em; - line-height: 1.2; - padding: 0.5em; -} -figure.highlight figcaption a, -pre .caption a, -pre figcaption a { - color: var(--highlight-foreground); - float: right; -} -figure.highlight figcaption a:hover, -pre .caption a:hover, -pre figcaption a:hover { - border-bottom-color: var(--highlight-foreground); -} -pre, -code { - font-family: consolas, Menlo, monospace, 'PingFang SC', 'Microsoft YaHei'; -} -code { - border-radius: 3px; - font-size: 0.875em; - padding: 2px 4px; - overflow-wrap: break-word; -} -kbd { - border: 2px solid #ccc; - border-radius: 0.2em; - box-shadow: 0.1em 0.1em 0.2em rgba(0,0,0,0.1); - font-family: inherit; - padding: 0.1em 0.3em; - white-space: nowrap; -} -figure.highlight { - overflow: auto; - position: relative; -} -figure.highlight pre { - border: 0; - margin: 0; - padding: 10px 0; -} -figure.highlight table { - border: 0; - margin: 0; - width: auto; -} -figure.highlight td { - border: 0; - padding: 0; -} -figure.highlight .gutter { - -moz-user-select: none; - -ms-user-select: none; - -webkit-user-select: none; - user-select: none; -} -figure.highlight .gutter pre { - background: var(--highlight-gutter-background); - color: var(--highlight-gutter-foreground); - padding-left: 10px; - padding-right: 10px; - text-align: right; -} -figure.highlight .code pre { - padding-left: 10px; - width: 100%; -} -figure.highlight .marked { - background: rgba(0,0,0,0.3); -} -pre .caption, -pre figcaption { - margin-bottom: 10px; -} -.gist table { - width: auto; -} -.gist table td { - border: 0; -} -pre { - overflow: auto; - padding: 10px; -} -pre code { - background: none; - padding: 0; - text-shadow: none; -} -.blockquote-center { - border-left: 0; - margin: 40px 0; - padding: 0; - position: relative; - text-align: center; -} -.blockquote-center::before, -.blockquote-center::after { - left: 0; - line-height: 1; - opacity: 0.6; - position: absolute; - width: 100%; -} -.blockquote-center::before { - border-top: 1px solid #ccc; - text-align: left; - top: -20px; - content: '\f10d'; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; -} -.blockquote-center::after { - border-bottom: 1px solid #ccc; - bottom: -20px; - text-align: right; - content: '\f10e'; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; -} -.blockquote-center p, -.blockquote-center div { - text-align: center; -} -.group-picture { - margin-bottom: 20px; -} -.group-picture .group-picture-row { - display: flex; - gap: 3px; - margin-bottom: 3px; -} -.group-picture .group-picture-column { - flex: 1; -} -.group-picture .group-picture-column img { - height: 100%; - margin: 0; - object-fit: cover; - width: 100%; -} -.post-body .label { - color: #555; - padding: 0 2px; -} -.post-body .label.default { - background: #f0f0f0; -} -.post-body .label.primary { - background: #efe6f7; -} -.post-body .label.info { - background: #e5f2f8; -} -.post-body .label.success { - background: #e7f4e9; -} -.post-body .label.warning { - background: #fcf6e1; -} -.post-body .label.danger { - background: #fae8eb; -} -.post-body .link-grid { - display: grid; - grid-gap: 1.5rem; - gap: 1.5rem; - grid-template-columns: 1fr 1fr; - margin-bottom: 20px; - padding: 1rem; -} -@media (max-width: 767px) { - .post-body .link-grid { - grid-template-columns: 1fr; - } -} -.post-body .link-grid .link-grid-container { - border: solid #ddd; - box-shadow: 1rem 1rem 0.5rem rgba(0,0,0,0.5); - min-height: 5rem; - min-width: 0; - padding: 0.5rem; - position: relative; - transition: background 0.3s; -} -.post-body .link-grid .link-grid-container:hover { - animation: next-shake 0.5s; - background: var(--card-bg-color); -} -.post-body .link-grid .link-grid-container:active { - box-shadow: 0.5rem 0.5rem 0.25rem rgba(0,0,0,0.5); - transform: translate(0.2rem, 0.2rem); -} -.post-body .link-grid .link-grid-container .link-grid-image { - border: 1px solid #ddd; - border-radius: 50%; - box-sizing: border-box; - height: 5rem; - padding: 3px; - position: absolute; - width: 5rem; -} -.post-body .link-grid .link-grid-container p { - margin: 0 1rem 0 6rem; -} -.post-body .link-grid .link-grid-container p:first-of-type { - font-size: 1.2em; -} -.post-body .link-grid .link-grid-container p:last-of-type { - font-size: 0.8em; - line-height: 1.3rem; - opacity: 0.7; -} -.post-body .link-grid .link-grid-container a { - border: 0; - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; -} -@keyframes next-shake { - 0% { - transform: translate(1pt, 1pt) rotate(0deg); - } - 10% { - transform: translate(-1pt, -2pt) rotate(-1deg); - } - 20% { - transform: translate(-3pt, 0pt) rotate(1deg); - } - 30% { - transform: translate(3pt, 2pt) rotate(0deg); - } - 40% { - transform: translate(1pt, -1pt) rotate(1deg); - } - 50% { - transform: translate(-1pt, 2pt) rotate(-1deg); - } - 60% { - transform: translate(-3pt, 1pt) rotate(0deg); - } - 70% { - transform: translate(3pt, 1pt) rotate(-1deg); - } - 80% { - transform: translate(-1pt, -1pt) rotate(1deg); - } - 90% { - transform: translate(1pt, 2pt) rotate(0deg); - } - 100% { - transform: translate(1pt, -2pt) rotate(-1deg); - } -} -.mermaid { - margin-bottom: 20px; - text-align: center; -} -.wavedrom { - margin-bottom: 20px; - text-align: center; -} -.post-body .note { - border-radius: 3px; - margin-bottom: 20px; - padding: 1em; - position: relative; - border: 1px solid #eee; - border-left-width: 5px; -} -.post-body .note summary { - cursor: pointer; - outline: 0; -} -.post-body .note summary p { - display: inline; -} -.post-body .note h2, -.post-body .note h3, -.post-body .note h4, -.post-body .note h5, -.post-body .note h6 { - border-bottom: initial; - margin: 0; - padding-top: 0; -} -.post-body .note :first-child { - margin-top: 0; -} -.post-body .note :last-child { - margin-bottom: 0; -} -.post-body .note:not(.no-icon) { - padding-left: 2.5em; -} -.post-body .note:not(.no-icon)::before { - font-size: 1.5em; - left: 0.3em; - position: absolute; - top: calc(50% - 1em); -} -.post-body .note.default { - border-left-color: #777; -} -.post-body .note.default h2, -.post-body .note.default h3, -.post-body .note.default h4, -.post-body .note.default h5, -.post-body .note.default h6 { - color: #777; -} -.post-body .note.default:not(.no-icon)::before { - content: '\f0a9'; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; - color: #777; -} -.post-body .note.primary { - border-left-color: #6f42c1; -} -.post-body .note.primary h2, -.post-body .note.primary h3, -.post-body .note.primary h4, -.post-body .note.primary h5, -.post-body .note.primary h6 { - color: #6f42c1; -} -.post-body .note.primary:not(.no-icon)::before { - content: '\f055'; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; - color: #6f42c1; -} -.post-body .note.info { - border-left-color: #428bca; -} -.post-body .note.info h2, -.post-body .note.info h3, -.post-body .note.info h4, -.post-body .note.info h5, -.post-body .note.info h6 { - color: #428bca; -} -.post-body .note.info:not(.no-icon)::before { - content: '\f05a'; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; - color: #428bca; -} -.post-body .note.success { - border-left-color: #5cb85c; -} -.post-body .note.success h2, -.post-body .note.success h3, -.post-body .note.success h4, -.post-body .note.success h5, -.post-body .note.success h6 { - color: #5cb85c; -} -.post-body .note.success:not(.no-icon)::before { - content: '\f058'; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; - color: #5cb85c; -} -.post-body .note.warning { - border-left-color: #f0ad4e; -} -.post-body .note.warning h2, -.post-body .note.warning h3, -.post-body .note.warning h4, -.post-body .note.warning h5, -.post-body .note.warning h6 { - color: #f0ad4e; -} -.post-body .note.warning:not(.no-icon)::before { - content: '\f06a'; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; - color: #f0ad4e; -} -.post-body .note.danger { - border-left-color: #d9534f; -} -.post-body .note.danger h2, -.post-body .note.danger h3, -.post-body .note.danger h4, -.post-body .note.danger h5, -.post-body .note.danger h6 { - color: #d9534f; -} -.post-body .note.danger:not(.no-icon)::before { - content: '\f056'; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; - color: #d9534f; -} -.post-body .tabs { - margin-bottom: 20px; -} -.post-body .tabs, -.tabs-comment { - padding-top: 10px; -} -.post-body .tabs ul.nav-tabs, -.tabs-comment ul.nav-tabs { - background: var(--body-bg-color); - display: flex; - display: flex; - flex-wrap: wrap; - justify-content: center; - margin: 0; - padding: 0; - position: -webkit-sticky; - position: sticky; - top: 0; - z-index: 5; -} -@media (max-width: 413px) { - .post-body .tabs ul.nav-tabs, - .tabs-comment ul.nav-tabs { - display: block; - margin-bottom: 5px; - } -} -.post-body .tabs ul.nav-tabs li.tab, -.tabs-comment ul.nav-tabs li.tab { - border-bottom: 1px solid #ddd; - border-left: 1px solid transparent; - border-right: 1px solid transparent; - border-radius: 0 0 0 0; - border-top: 3px solid transparent; - flex-grow: 1; - list-style-type: none; - transition: all 0.2s ease-out; -} -@media (max-width: 413px) { - .post-body .tabs ul.nav-tabs li.tab, - .tabs-comment ul.nav-tabs li.tab { - border-bottom: 1px solid transparent; - border-left: 3px solid transparent; - border-right: 1px solid transparent; - border-top: 1px solid transparent; - } -} -@media (max-width: 413px) { - .post-body .tabs ul.nav-tabs li.tab, - .tabs-comment ul.nav-tabs li.tab { - border-radius: 0; - } -} -.post-body .tabs ul.nav-tabs li.tab a, -.tabs-comment ul.nav-tabs li.tab a { - border-bottom: initial; - display: block; - line-height: 1.8; - padding: 0.25em 0.75em; - text-align: center; - transition: all 0.2s ease-out; -} -.post-body .tabs ul.nav-tabs li.tab a i[class^='fa'], -.tabs-comment ul.nav-tabs li.tab a i[class^='fa'] { - width: 1.285714285714286em; -} -.post-body .tabs ul.nav-tabs li.tab.active, -.tabs-comment ul.nav-tabs li.tab.active { - border-bottom-color: transparent; - border-left-color: #ddd; - border-right-color: #ddd; - border-top-color: #fc6423; -} -@media (max-width: 413px) { - .post-body .tabs ul.nav-tabs li.tab.active, - .tabs-comment ul.nav-tabs li.tab.active { - border-bottom-color: #ddd; - border-left-color: #fc6423; - border-right-color: #ddd; - border-top-color: #ddd; - } -} -.post-body .tabs ul.nav-tabs li.tab.active a, -.tabs-comment ul.nav-tabs li.tab.active a { - cursor: default; -} -.post-body .tabs .tab-content, -.tabs-comment .tab-content { - border: 1px solid #ddd; - border-radius: 0 0 0 0; - border-top-color: transparent; -} -@media (max-width: 413px) { - .post-body .tabs .tab-content, - .tabs-comment .tab-content { - border-radius: 0; - border-top-color: #ddd; - } -} -.post-body .tabs .tab-content .tab-pane, -.tabs-comment .tab-content .tab-pane { - padding: 20px 20px 0; -} -.post-body .tabs .tab-content .tab-pane:not(.active), -.tabs-comment .tab-content .tab-pane:not(.active) { - display: none; -} -.pagination .prev, -.pagination .next, -.pagination .page-number, -.pagination .space { - display: inline-block; - margin: -1px 10px 0; - padding: 0 10px; -} -@media (max-width: 767px) { - .pagination .prev, - .pagination .next, - .pagination .page-number, - .pagination .space { - margin: 0 5px; - } -} -.pagination .page-number.current { - background: #ccc; - border-color: #ccc; - color: var(--content-bg-color); -} -.pagination { - border-top: 1px solid #eee; - margin: 120px 0 0; - text-align: left; -} -.pagination .prev, -.pagination .next, -.pagination .page-number { - border-bottom: 0; - border-top: 1px solid #eee; - transition: border-color 0.2s ease-in-out; -} -.pagination .prev:hover, -.pagination .next:hover, -.pagination .page-number:hover { - border-top-color: var(--link-hover-color); -} -@media (max-width: 767px) { - .pagination { - border-top: 0; - } - .pagination .prev, - .pagination .next, - .pagination .page-number { - border-bottom: 1px solid #eee; - border-top: 0; - } - .pagination .prev:hover, - .pagination .next:hover, - .pagination .page-number:hover { - border-bottom-color: var(--link-hover-color); - } -} -.pagination .space { - margin: 0; - padding: 0; -} -.comments { - margin-top: 60px; - overflow: hidden; -} -.comment-button-group { - display: flex; - display: flex; - flex-wrap: wrap; - justify-content: center; - justify-content: center; - margin: 1em 0; -} -.comment-button-group .comment-button { - margin: 0.1em 0.2em; -} -.comment-button-group .comment-button.active { - background: var(--btn-default-hover-bg); - border-color: var(--btn-default-hover-border-color); - color: var(--btn-default-hover-color); -} -.comment-position { - display: none; -} -.comment-position.active { - display: block; -} -.tabs-comment { - margin-top: 4em; - padding-top: 0; -} -.tabs-comment .comments { - margin-top: 0; - padding-top: 0; -} -.headband { - background: var(--theme-color); - height: 3px; -} -@media (max-width: 991px) { - .headband { - display: none; - } -} -.site-brand-container { - display: flex; - flex-shrink: 0; - padding: 0 10px; -} -.use-motion .column, -.use-motion .site-brand-container .toggle { - opacity: 0; -} -.site-meta { - flex-grow: 1; - text-align: center; -} -@media (max-width: 767px) { - .site-meta { - text-align: center; - } -} -.custom-logo-image { - margin-top: 20px; -} -@media (max-width: 991px) { - .custom-logo-image { - display: none; - } -} -.brand { - border-bottom: 0; - color: var(--brand-color); - display: inline-block; - padding: 2px 1px; -} -.brand:hover { - color: var(--brand-hover-color); -} -.site-title { - font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; - font-size: 1.375em; - font-weight: normal; - line-height: 1.5; - margin: 0; -} -.site-subtitle { - color: #999; - font-size: 0.8125em; - margin: 10px 0; -} -.use-motion .site-title, -.use-motion .site-subtitle, -.use-motion .custom-logo-image { - opacity: 0; - position: relative; - top: -10px; -} -.site-nav-toggle, -.site-nav-right { - display: none; -} -@media (max-width: 767px) { - .site-nav-toggle, - .site-nav-right { - display: flex; - flex-direction: column; - justify-content: center; - } -} -.site-nav-toggle .toggle, -.site-nav-right .toggle { - color: var(--text-color); - padding: 10px; - width: 22px; -} -.site-nav-toggle .toggle .toggle-line, -.site-nav-right .toggle .toggle-line { - background: var(--text-color); - border-radius: 1px; -} -@media (max-width: 767px) { - .site-nav { - --scroll-height: 0; - height: 0; - overflow: hidden; - transition: 0.2s ease-in-out; - transition-property: height, visibility; - visibility: hidden; - } - body:not(.site-nav-on) .site-nav .animated { - animation: none; - } - body.site-nav-on .site-nav { - height: var(--scroll-height); - visibility: unset; - } -} -.menu { - margin: 0; - padding: 1em 0; - text-align: center; -} -.menu-item { - display: inline-block; - list-style: none; - margin: 0 10px; -} -@media (max-width: 767px) { - .menu-item { - display: block; - margin-top: 10px; - } - .menu-item.menu-item-search { - display: none; - } -} -.menu-item a { - border-bottom: 0; - display: block; - font-size: 0.8125em; - transition: border-color 0.2s ease-in-out; -} -.menu-item a:hover, -.menu-item a.menu-item-active { - background: var(--menu-item-bg-color); -} -.menu-item i[class^='fa'] { - margin-right: 8px; -} -.menu-item .badge { - background: #fff; - border-radius: 10px; - color: #555; - font-weight: bold; - line-height: 1; - margin-left: 0.35em; - padding: 1px 4px; - text-shadow: 1px 1px 0 rgba(0,0,0,0.1); -} -.use-motion .menu-item { - visibility: hidden; -} -.sidebar { - right: -320px; -} -.sidebar-active .sidebar { - right: 0; -} -.sidebar { - background: #222; - bottom: 0; - box-shadow: inset 0 2px 6px #000; - max-height: 100vh; - overflow-y: auto; - position: fixed; - top: 0; - transition: all 0.2s ease-out; - width: 320px; - z-index: 20; -} -.sidebar a { - border-bottom-color: #555; - color: #999; -} -.sidebar a:hover { - border-bottom-color: #eee; - color: #eee; -} -.links-of-author:not(:first-child) { - margin-top: 15px; -} -.links-of-author a { - border-bottom-color: #555; - display: inline-block; - margin-bottom: 10px; - margin-right: 10px; - vertical-align: middle; - transition: all 0.2s ease-in-out; -} -.links-of-author a::before { - background: #fccf78; - display: inline-block; - margin-right: 3px; - transform: translateY(-2px); - border-radius: 50%; - content: ' '; - height: 4px; - width: 4px; -} -.links-of-blogroll-item { - padding: 2px 10px; -} -.links-of-blogroll-item a { - box-sizing: border-box; - display: inline-block; - max-width: 280px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.popular-posts .popular-posts-item .popular-posts-link:hover { - background: none; -} -.sidebar-dimmer { - background: #000; - height: 100%; - left: 0; - opacity: 0; - position: fixed; - top: 0; - transition: visibility 0.4s, opacity 0.4s; - visibility: hidden; - width: 100%; - z-index: 10; -} -.sidebar-active .sidebar-dimmer { - opacity: 0.7; - visibility: visible; -} -.sidebar-inner { - color: #999; - padding: 18px 10px; - text-align: center; - display: flex; - flex-direction: column; - justify-content: center; -} -.cc-license .cc-opacity { - border-bottom: 0; - opacity: 0.7; -} -.cc-license .cc-opacity:hover { - opacity: 0.9; -} -.cc-license img { - display: inline-block; -} -.site-author-image { - border: 2px solid #333; - max-width: 96px; - padding: 2px; -} -.site-author-name { - color: #f5f5f5; - font-weight: normal; - margin: 5px 0 0; -} -.site-description { - color: #999; - font-size: 1em; - margin-top: 5px; -} -.links-of-author a { - font-size: 0.8125em; -} -.links-of-author i[class^='fa'] { - margin-right: 2px; -} -.sidebar .sidebar-button:not(:first-child) { - margin-top: 15px; -} -.sidebar .sidebar-button button { - background: transparent; - color: #fc6423; - cursor: pointer; - line-height: 2; - padding: 0 15px; - border: 1px solid #fc6423; - border-radius: 4px; -} -.sidebar .sidebar-button button:hover { - background: #fc6423; - color: #fff; -} -.sidebar .sidebar-button button i[class^='fa'] { - margin-right: 5px; -} -.links-of-blogroll { - font-size: 0.8125em; -} -.links-of-blogroll-title { - font-size: 0.875em; - font-weight: 600; -} -.links-of-blogroll-list { - list-style: none; - margin: 0; - padding: 0; -} -.sidebar-nav { - font-size: 0.875em; - height: 0; - margin: 0; - overflow: hidden; - padding-left: 0; - pointer-events: none; - transition: 0.2s ease-in-out; - transition-property: height, visibility; - visibility: hidden; -} -.sidebar-nav-active .sidebar-nav { - height: calc(2em + 1px); - pointer-events: unset; - visibility: unset; -} -.sidebar-nav li { - border-bottom: 1px solid transparent; - color: #666; - cursor: pointer; - display: inline-block; - transition: 0.2s ease-in-out; - transition-property: border-bottom-color, color; -} -.sidebar-nav li.sidebar-nav-overview { - margin-left: 10px; -} -.sidebar-nav li:hover { - color: #f5f5f5; -} -.sidebar-toc-active .sidebar-nav-toc, -.sidebar-overview-active .sidebar-nav-overview { - border-bottom-color: #87daff; - color: #87daff; - transition-delay: 0.2s; -} -.sidebar-toc-active .sidebar-nav-toc:hover, -.sidebar-overview-active .sidebar-nav-overview:hover { - color: #87daff; -} -.sidebar-panel-container { - align-items: start; - display: grid; - flex: 1; - overflow-x: hidden; - overflow-y: auto; - padding-top: 0; - transition: padding-top 0.2s ease-in-out; -} -.sidebar-nav-active .sidebar-panel-container { - padding-top: 20px; -} -.sidebar-panel { - animation: deactivate-sidebar-panel 0.2s ease-in-out; - grid-area: 1/1; - height: 0; - opacity: 0; - overflow: hidden; - pointer-events: none; - transform: translateY(0); - transition: 0.2s ease-in-out; - transition-delay: 0s; - transition-property: opacity, transform, visibility; - visibility: hidden; -} -.sidebar-nav-active .sidebar-panel, -.sidebar-overview-active .sidebar-panel.post-toc-wrap { - transform: translateY(-20px); -} -.sidebar-overview-active:not(.sidebar-nav-active) .sidebar-panel.post-toc-wrap { - transition-delay: 0s, 0.2s, 0s; -} -.sidebar-overview-active .sidebar-panel.site-overview-wrap, -.sidebar-toc-active .sidebar-panel.post-toc-wrap { - animation-name: activate-sidebar-panel; - height: auto; - opacity: 1; - pointer-events: unset; - transform: translateY(0); - transition-delay: 0.2s, 0.2s, 0s; - visibility: unset; -} -.sidebar-panel.site-overview-wrap { - display: flex; - flex-direction: column; - justify-content: center; - gap: 10px; - justify-content: flex-start; -} -@keyframes deactivate-sidebar-panel { - from { - height: var(--inactive-panel-height, 0); - } - to { - height: var(--active-panel-height, 0); - } -} -@keyframes activate-sidebar-panel { - from { - height: var(--inactive-panel-height, auto); - } - to { - height: var(--active-panel-height, auto); - } -} -.sidebar-toggle { - bottom: 61px; - height: 16px; - padding: 5px; - width: 16px; - background: #222; - cursor: pointer; - opacity: 0.8; - position: fixed; - z-index: 30; - right: 30px; -} -@media (max-width: 991px) { - .sidebar-toggle { - right: 20px; - } -} -.sidebar-toggle:hover { - opacity: 1; -} -@media (max-width: 991px) { - .sidebar-toggle { - opacity: 1; - } -} -.sidebar-toggle:hover .toggle-line { - background: #87daff; -} -@media (any-hover: hover) { - body:not(.sidebar-active) .sidebar-toggle:hover :first-child { - top: 2px; - transform: rotate(-45deg); - width: 50%; - } - body:not(.sidebar-active) .sidebar-toggle:hover :last-child { - top: -2px; - transform: rotate(45deg); - width: 50%; - } -} -.sidebar-active .sidebar-toggle :nth-child(2) { - opacity: 0; -} -.sidebar-active .sidebar-toggle :first-child { - top: 6px; - transform: rotate(-45deg); -} -.sidebar-active .sidebar-toggle :last-child { - top: -6px; - transform: rotate(45deg); -} -.post-toc { - font-size: 0.875em; -} -.post-toc ol { - list-style: none; - margin: 0; - padding: 0 2px 0 10px; - text-align: left; -} -.post-toc ol > :last-child { - margin-bottom: 5px; -} -.post-toc ol > ol { - padding-left: 0; -} -.post-toc ol a { - transition: all 0.2s ease-in-out; -} -.post-toc .nav-item { - line-height: 1.8; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.post-toc .nav .nav-child { - --height: 0; - height: 0; - opacity: 0; - overflow: hidden; - transition-property: height, opacity, visibility; - transition: 0.2s ease-in-out; - visibility: hidden; -} -.post-toc .nav .active > .nav-child { - height: var(--height, auto); - opacity: 1; - visibility: unset; -} -.post-toc .nav .active > a { - border-bottom-color: #87daff; - color: #87daff; -} -.post-toc .nav .active-current > a { - color: #87daff; -} -.post-toc .nav .active-current > a:hover { - color: #87daff; -} -.site-state { - display: flex; - flex-wrap: wrap; - justify-content: center; - line-height: 1.4; -} -.site-state-item { - padding: 0 15px; -} -.site-state-item a { - border-bottom: 0; - display: block; -} -.site-state-item-count { - display: block; - font-size: 1.25em; - font-weight: 600; -} -.site-state-item-name { - color: inherit; - font-size: 0.875em; -} -.footer { - color: #999; - font-size: 0.875em; - padding: 20px 0; - transition: 0.2s ease-in-out; - transition-property: left, right; -} -.footer.footer-fixed { - bottom: 0; - left: 0; - position: absolute; - right: 0; -} -.footer-inner { - box-sizing: border-box; - text-align: left; - display: flex; - flex-direction: column; - justify-content: center; - margin: 0 auto; - width: 700px; -} -@media (max-width: 767px) { - .footer-inner { - width: auto; - } -} -@media (min-width: 1200px) { - .footer-inner { - width: 800px; - } -} -@media (min-width: 1600px) { - .footer-inner { - width: 900px; - } -} -.use-motion .footer { - opacity: 0; -} -.languages { - display: inline-block; - font-size: 1.125em; - position: relative; -} -.languages .lang-select-label span { - margin: 0 0.5em; -} -.languages .lang-select { - height: 100%; - left: 0; - opacity: 0; - position: absolute; - top: 0; - width: 100%; -} -.with-love { - color: #ff66b3; - display: inline-block; - margin: 0 5px; - animation: icon-animate 1.33s ease-in-out infinite; -} -@keyframes icon-animate { - 0%, 100% { - transform: scale(1); - } - 10%, 30% { - transform: scale(0.9); - } - 20%, 40%, 60%, 80% { - transform: scale(1.1); - } - 50%, 70% { - transform: scale(1.1); - } -} -@media (max-width: 567px) { - .main-inner { - padding: initial !important; - } - .posts-expand .post-header { - margin-bottom: 10px !important; - } - .post-block { - margin-top: initial !important; - padding: 8px 18px 8px !important; - } - .post-body h1, - .post-body h2, - .post-body h3, - .post-body h4, - .post-body h5, - .post-body h6 { - margin: 20px 0 8px; - } - .post-body .note h1, - .post-body .tabs .tab-content .tab-pane h1, - .post-body .note h2, - .post-body .tabs .tab-content .tab-pane h2, - .post-body .note h3, - .post-body .tabs .tab-content .tab-pane h3, - .post-body .note h4, - .post-body .tabs .tab-content .tab-pane h4, - .post-body .note h5, - .post-body .tabs .tab-content .tab-pane h5, - .post-body .note h6, - .post-body .tabs .tab-content .tab-pane h6 { - margin: 0 5px; - } - .post-body > p { - margin: 0 0 10px; - } - .post-body .note > p, - .post-body .tabs .tab-content .tab-pane > p { - padding: 0 5px; - } - .post-body img, - .post-body video { - margin-bottom: 10px !important; - } - .post-body img + figcaption, - .post-body .fancybox + figcaption { - margin: -5px auto 15px !important; - } - .post-body .note { - margin-bottom: 10px !important; - padding: 10px !important; - } - .post-body .note:not(.no-icon) { - padding-left: 35px !important; - } - .post-body .tabs .tab-content .tab-pane { - padding: 10px 10px 0 !important; - } - .post-eof { - margin: 40px auto 20px !important; - } - .pagination { - margin-top: 40px; - } -} -.back-to-top { - font-size: 12px; - align-items: center; - bottom: -100px; - color: #fff; - display: flex; - height: 26px; - transition: bottom 0.2s ease-in-out; - background: #222; - cursor: pointer; - opacity: 0.8; - position: fixed; - z-index: 30; - right: 30px; -} -.back-to-top span { - margin-right: 8px; -} -.back-to-top .fa { - text-align: center; - width: 26px; -} -@media (max-width: 991px) { - .back-to-top { - right: 20px; - } -} -.back-to-top:hover { - opacity: 1; -} -@media (max-width: 991px) { - .back-to-top { - opacity: 1; - } -} -.back-to-top:hover { - color: #87daff; -} -.back-to-top.back-to-top-on { - bottom: 30px; -} -.reading-progress-bar { - --progress: 0; - background: #37c6c0; - height: 3px; - position: fixed; - z-index: 50; - width: calc(100% - var(--progress)); - left: 0; - top: 0; -} -.rtl.post-body p, -.rtl.post-body a, -.rtl.post-body h1, -.rtl.post-body h2, -.rtl.post-body h3, -.rtl.post-body h4, -.rtl.post-body h5, -.rtl.post-body h6, -.rtl.post-body li, -.rtl.post-body ul, -.rtl.post-body ol { - direction: rtl; - font-family: UKIJ Ekran; -} -.rtl.post-title { - font-family: UKIJ Ekran; -} -.post-button { - margin-top: 40px; - text-align: left; -} -.use-motion .post-block, -.use-motion .pagination, -.use-motion .comments { - visibility: hidden; -} -.use-motion .post-header { - visibility: hidden; -} -.use-motion .post-body { - visibility: hidden; -} -.use-motion .collection-header { - visibility: hidden; -} -.posts-collapse .post-content { - margin-bottom: 35px; - margin-left: 35px; - position: relative; -} -@media (max-width: 767px) { - .posts-collapse .post-content { - margin-left: 0; - margin-right: 0; - } -} -.posts-collapse .post-content .collection-title { - font-size: 1.125em; - position: relative; -} -.posts-collapse .post-content .collection-title::before { - background: #999; - border: 1px solid #fff; - margin-left: -6px; - margin-top: -4px; - position: absolute; - top: 50%; - border-radius: 50%; - content: ' '; - height: 10px; - width: 10px; -} -.posts-collapse .post-content .collection-year { - font-size: 1.5em; - font-weight: bold; - margin: 60px 0; - position: relative; -} -.posts-collapse .post-content .collection-year .collection-year-count { - font-size: 0.75em; - background: #fff; - border-radius: 10px; - color: #555; - font-weight: bold; - line-height: 1; - margin-left: 0.35em; - padding: 1px 4px; - text-shadow: 1px 1px 0 rgba(0,0,0,0.1); -} -.posts-collapse .post-content .collection-year::before { - background: #bbb; - margin-left: -4px; - margin-top: -4px; - position: absolute; - top: 50%; - border-radius: 50%; - content: ' '; - height: 8px; - width: 8px; -} -.posts-collapse .post-content .collection-header { - display: block; - margin-left: 20px; -} -.posts-collapse .post-content .collection-header small { - color: #bbb; - margin-left: 5px; -} -.posts-collapse .post-content .post-header { - border-bottom: 1px dashed #ccc; - margin: 30px 2px 0; - padding-left: 15px; - position: relative; - transition: border 0.2s ease-in-out; -} -.posts-collapse .post-content .post-header::before { - background: #bbb; - border: 1px solid #fff; - left: -6px; - position: absolute; - top: 0.75em; - transition: background 0.2s ease-in-out; - border-radius: 50%; - content: ' '; - height: 6px; - width: 6px; -} -.posts-collapse .post-content .post-header:hover { - border-bottom-color: #666; -} -.posts-collapse .post-content .post-header:hover::before { - background: #222; -} -.posts-collapse .post-content .post-meta-container { - display: inline; - font-size: 0.75em; - margin-right: 10px; -} -.posts-collapse .post-content .post-title { - display: inline; -} -.posts-collapse .post-content .post-title a { - border-bottom: 0; - color: var(--link-color); -} -.posts-collapse .post-content .post-title .fa { - font-size: 0.875em; - margin-left: 5px; -} -.posts-collapse .post-content::before { - background: #f5f5f5; - content: ' '; - height: 100%; - margin-left: -2px; - position: absolute; - top: 1.25em; - width: 4px; -} -.post-body { - font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; - overflow-wrap: break-word; -} -@media (min-width: 1200px) { - .post-body { - font-size: 1.125em; - } -} -@media (min-width: 992px) { - .post-body { - text-align: justify; - } -} -@media (max-width: 991px) { - .post-body { - text-align: justify; - } -} -.post-body h1 .header-anchor, -.post-body h2 .header-anchor, -.post-body h3 .header-anchor, -.post-body h4 .header-anchor, -.post-body h5 .header-anchor, -.post-body h6 .header-anchor, -.post-body h1 .headerlink, -.post-body h2 .headerlink, -.post-body h3 .headerlink, -.post-body h4 .headerlink, -.post-body h5 .headerlink, -.post-body h6 .headerlink { - border-bottom-style: none; - color: inherit; - float: right; - font-size: 0.875em; - margin-left: 10px; - opacity: 0; -} -.post-body h1 .header-anchor::before, -.post-body h2 .header-anchor::before, -.post-body h3 .header-anchor::before, -.post-body h4 .header-anchor::before, -.post-body h5 .header-anchor::before, -.post-body h6 .header-anchor::before, -.post-body h1 .headerlink::before, -.post-body h2 .headerlink::before, -.post-body h3 .headerlink::before, -.post-body h4 .headerlink::before, -.post-body h5 .headerlink::before, -.post-body h6 .headerlink::before { - content: '\f0c1'; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; -} -.post-body h1:hover .header-anchor, -.post-body h2:hover .header-anchor, -.post-body h3:hover .header-anchor, -.post-body h4:hover .header-anchor, -.post-body h5:hover .header-anchor, -.post-body h6:hover .header-anchor, -.post-body h1:hover .headerlink, -.post-body h2:hover .headerlink, -.post-body h3:hover .headerlink, -.post-body h4:hover .headerlink, -.post-body h5:hover .headerlink, -.post-body h6:hover .headerlink { - opacity: 0.5; -} -.post-body h1:hover .header-anchor:hover, -.post-body h2:hover .header-anchor:hover, -.post-body h3:hover .header-anchor:hover, -.post-body h4:hover .header-anchor:hover, -.post-body h5:hover .header-anchor:hover, -.post-body h6:hover .header-anchor:hover, -.post-body h1:hover .headerlink:hover, -.post-body h2:hover .headerlink:hover, -.post-body h3:hover .headerlink:hover, -.post-body h4:hover .headerlink:hover, -.post-body h5:hover .headerlink:hover, -.post-body h6:hover .headerlink:hover { - opacity: 1; -} -.post-body .exturl .fa { - font-size: 0.875em; - margin-left: 4px; -} -.post-body img + figcaption, -.post-body .fancybox + figcaption { - color: #999; - font-size: 0.875em; - font-weight: bold; - line-height: 1; - margin: -15px auto 15px; - text-align: center; -} -.post-body iframe, -.post-body img, -.post-body video, -.post-body embed { - margin-bottom: 20px; -} -.post-body .video-container { - height: 0; - margin-bottom: 20px; - overflow: hidden; - padding-top: 75%; - position: relative; - width: 100%; -} -.post-body .video-container iframe, -.post-body .video-container object, -.post-body .video-container embed { - height: 100%; - left: 0; - margin: 0; - position: absolute; - top: 0; - width: 100%; -} -.post-gallery { - display: flex; - min-height: 200px; -} -.post-gallery .post-gallery-image { - flex: 1; -} -.post-gallery .post-gallery-image:not(:first-child) { - clip-path: polygon(40px 0, 100% 0, 100% 100%, 0 100%); - margin-left: -20px; -} -.post-gallery .post-gallery-image:not(:last-child) { - margin-right: -20px; -} -.post-gallery .post-gallery-image img { - height: 100%; - object-fit: cover; - opacity: 1; - width: 100%; -} -.posts-expand .post-gallery { - margin-bottom: 60px; -} -.posts-collapse .post-gallery { - margin: 15px 0; -} -.posts-expand .post-header { - font-size: 1.125em; - margin-bottom: 60px; - text-align: center; -} -.posts-expand .post-title { - font-size: 1.5em; - font-weight: normal; - margin: initial; - overflow-wrap: break-word; -} -.posts-expand .post-title-link { - border-bottom: 0; - color: var(--link-color); - display: inline-block; - position: relative; -} -.posts-expand .post-title-link::before { - background: var(--link-color); - bottom: 0; - content: ''; - height: 2px; - left: 0; - position: absolute; - transform: scaleX(0); - transition: transform 0.2s ease-in-out; - width: 100%; -} -.posts-expand .post-title-link:hover::before { - transform: scaleX(1); -} -.posts-expand .post-title-link .fa { - font-size: 0.875em; - margin-left: 5px; -} -.post-sticky-flag { - display: inline-block; - margin-right: 8px; - transform: rotate(30deg); -} -.posts-expand .post-meta-container { - color: #999; - font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; - font-size: 0.75em; - margin-top: 3px; -} -.posts-expand .post-meta-container .post-description { - font-size: 0.875em; - margin-top: 2px; -} -.posts-expand .post-meta-container time { - border-bottom: 1px dashed #999; -} -.post-meta { - display: flex; - flex-wrap: wrap; - justify-content: center; -} -:not(.post-meta-break) + .post-meta-item::before { - content: '|'; - margin: 0 0.5em; -} -.post-meta-item-icon { - margin-right: 3px; -} -@media (max-width: 991px) { - .post-meta-item-text { - display: none; - } -} -.post-meta-break { - flex-basis: 100%; - height: 0; -} -.post-nav { - border-top: 1px solid #eee; - display: flex; - gap: 30px; - justify-content: space-between; - margin-top: 1em; - padding: 10px 5px 0; -} -.post-nav-item { - flex: 1; -} -.post-nav-item a { - border-bottom: 0; - display: block; - font-size: 0.875em; - line-height: 1.6; -} -.post-nav-item a:active { - top: 2px; -} -.post-nav-item .fa { - font-size: 0.75em; -} -.post-nav-item:first-child .fa { - margin-right: 5px; -} -.post-nav-item:last-child { - text-align: right; -} -.post-nav-item:last-child .fa { - margin-left: 5px; -} -.post-footer { - display: flex; - flex-direction: column; - justify-content: center; -} -.post-eof { - background: #ccc; - height: 1px; - margin: 80px auto 60px; - width: 8%; -} -.post-block:last-of-type .post-eof { - display: none; -} -.post-copyright ul { - list-style: none; - overflow: hidden; - padding: 0.5em 1em; - position: relative; - background: var(--card-bg-color); - border-left: 3px solid #ff2a2a; - margin: 1em 0 0; -} -.post-copyright ul::after { - content: '\f25e'; - font-family: 'Font Awesome 6 Brands'; - font-size: 200px; - opacity: 0.1; - position: absolute; - right: -50px; - top: -150px; -} -.post-tags { - margin-top: 40px; - text-align: left; -} -.post-tags a { - display: inline-block; - font-size: 0.8125em; -} -.post-tags a:not(:last-child) { - margin-right: 10px; -} -.social-like { - border-top: 1px solid #eee; - font-size: 0.875em; - margin-top: 1em; - padding-top: 1em; - display: flex; - flex-wrap: wrap; - justify-content: center; -} -.social-like a { - border-bottom: none; -} -.reward-container { - margin: 1em 0 0; - padding: 1em 0; - text-align: center; -} -.reward-container button { - background: transparent; - color: #87daff; - cursor: pointer; - line-height: 2; - padding: 0 15px; - border: 2px solid #87daff; - border-radius: 2px; - outline: 0; - transition: all 0.2s ease-in-out; - vertical-align: text-top; -} -.reward-container button:hover { - background: #87daff; - color: #fff; -} -.post-reward { - display: none; - padding-top: 20px; -} -.post-reward.active { - display: block; -} -.post-reward div { - display: inline-block; -} -.post-reward div span { - display: block; -} -.post-reward div:hover span { - animation: next-roll 0.1s infinite linear; - pointer-events: none; -} -.post-reward img { - display: inline-block; - margin: 0.8em 2em 0; - max-width: 100%; - width: 180px; -} -@keyframes next-roll { - from { - transform: rotateZ(30deg); - } - to { - transform: rotateZ(-30deg); - } -} -.followme { - color: #bbb; - padding: 1em 1.5em; - text-align: center; - background: var(--card-bg-color); - border-left: 3px solid #ff2a2a; - margin: 1em 0 0; -} -.followme .social-list { - display: flex; - flex-wrap: wrap; - justify-content: center; -} -.followme .social-list .social-item { - margin: 0.5em 2em; - position: relative; -} -@media (max-width: 991px) { - .followme .social-list .social-item { - margin: 0.5em 0.75em; - } -} -.followme .social-list .social-link { - border: 0; - display: block; -} -.followme .social-list .social-link .icon { - font-size: 1.75em; -} -.followme .social-list .social-link .label { - display: block; - font-size: 14px; -} -.followme .social-list .social-link:hover + .social-item-img { - display: block; -} -.followme .social-list span.social-link { - color: var(--link-color); -} -.followme .social-list span.social-link:hover { - color: var(--link-hover-color); -} -.followme .social-list .social-item-img { - display: none; - left: 50%; - max-width: 180px; - position: absolute; - transform: translate(-50%, 20px); -} -.category-all-page .category-all-title { - text-align: center; -} -.category-all-page .category-all { - margin-top: 20px; -} -.category-all-page .category-list { - list-style: none; - margin: 0; - padding: 0; -} -.category-all-page .category-list-item { - margin: 5px 10px; -} -.category-all-page .category-list-count { - font-size: 0.75em; - background: #fff; - border-radius: 10px; - color: #555; - font-weight: bold; - line-height: 1; - margin-left: 0.35em; - padding: 1px 4px; - text-shadow: 1px 1px 0 rgba(0,0,0,0.1); -} -.category-all-page .category-list-child { - padding-left: 10px; -} -.event-list hr { - background: #222; - margin: 20px 0 45px; -} -.event-list hr::after { - background: #222; - color: #fff; - content: 'NOW'; - display: inline-block; - font-weight: bold; - padding: 0 5px; -} -.event-list .event { - --event-background: #222; - --event-foreground: #bbb; - --event-title: #fff; - background: var(--event-background); - padding: 15px; -} -.event-list .event .event-summary { - border-bottom: 0; - color: var(--event-title); - margin: 0; - padding: 0 0 0 35px; - position: relative; -} -.event-list .event .event-summary::before { - animation: dot-flash 1s alternate infinite ease-in-out; - background: var(--event-title); - left: 0; - margin-top: -6px; - position: absolute; - top: 50%; - border-radius: 50%; - content: ' '; - height: 12px; - width: 12px; -} -.event-list .event:nth-of-type(odd) .event-summary::before { - animation-delay: 0.5s; -} -.event-list .event:not(:last-child) { - margin-bottom: 20px; -} -.event-list .event .event-relative-time { - color: var(--event-foreground); - display: inline-block; - font-size: 12px; - font-weight: normal; - padding-left: 12px; -} -.event-list .event .event-details { - color: var(--event-foreground); - display: block; - line-height: 18px; - padding: 6px 0 6px 35px; -} -.event-list .event .event-details::before { - color: var(--event-foreground); - display: inline-block; - margin-right: 9px; - width: 14px; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; -} -.event-list .event .event-details.event-location::before { - content: '\f041'; -} -.event-list .event .event-details.event-duration::before { - content: '\f017'; -} -.event-list .event .event-details.event-description::before { - content: '\f024'; -} -.event-list .event-past { - --event-background: #f5f5f5; - --event-foreground: #999; - --event-title: #222; -} -@keyframes dot-flash { - from { - opacity: 1; - transform: scale(1); - } - to { - opacity: 0; - transform: scale(0.8); - } -} -ul.breadcrumb { - font-size: 0.75em; - list-style: none; - margin: 1em 0; - padding: 0 2em; - text-align: center; -} -ul.breadcrumb li { - display: inline; -} -ul.breadcrumb li:not(:first-child)::before { - content: '/\00a0'; - font-weight: normal; - padding: 0.5em; -} -ul.breadcrumb li:last-child { - font-weight: bold; -} -.tag-cloud { - text-align: center; -} -.tag-cloud a { - display: inline-block; - margin: 10px; -} -.tag-cloud-0 { - border-bottom-color: #aaa; - color: #aaa; -} -.tag-cloud-1 { - border-bottom-color: #9a9a9a; - color: #9a9a9a; -} -.tag-cloud-2 { - border-bottom-color: #8b8b8b; - color: #8b8b8b; -} -.tag-cloud-3 { - border-bottom-color: #7c7c7c; - color: #7c7c7c; -} -.tag-cloud-4 { - border-bottom-color: #6c6c6c; - color: #6c6c6c; -} -.tag-cloud-5 { - border-bottom-color: #5d5d5d; - color: #5d5d5d; -} -.tag-cloud-6 { - border-bottom-color: #4e4e4e; - color: #4e4e4e; -} -.tag-cloud-7 { - border-bottom-color: #3e3e3e; - color: #3e3e3e; -} -.tag-cloud-8 { - border-bottom-color: #2f2f2f; - color: #2f2f2f; -} -.tag-cloud-9 { - border-bottom-color: #202020; - color: #202020; -} -.tag-cloud-10 { - border-bottom-color: #111; - color: #111; -} -@media (prefers-color-scheme: dark) { - .tag-cloud-0 { - border-bottom-color: #555; - color: #555; - } - .tag-cloud-1 { - border-bottom-color: #646464; - color: #646464; - } - .tag-cloud-2 { - border-bottom-color: #737373; - color: #737373; - } - .tag-cloud-3 { - border-bottom-color: #828282; - color: #828282; - } - .tag-cloud-4 { - border-bottom-color: #929292; - color: #929292; - } - .tag-cloud-5 { - border-bottom-color: #a1a1a1; - color: #a1a1a1; - } - .tag-cloud-6 { - border-bottom-color: #b0b0b0; - color: #b0b0b0; - } - .tag-cloud-7 { - border-bottom-color: #c0c0c0; - color: #c0c0c0; - } - .tag-cloud-8 { - border-bottom-color: #cfcfcf; - color: #cfcfcf; - } - .tag-cloud-9 { - border-bottom-color: #dedede; - color: #dedede; - } - .tag-cloud-10 { - border-bottom-color: #eee; - color: #eee; - } -} -.utterances { - max-width: unset; -} -.search-active { - overflow: hidden; -} -.search-pop-overlay { - background: rgba(0,0,0,0); - display: flex; - height: 100%; - left: 0; - position: fixed; - top: 0; - transition: visibility 0.4s, background 0.4s; - visibility: hidden; - width: 100%; - z-index: 40; -} -.search-active .search-pop-overlay { - background: rgba(0,0,0,0.3); - visibility: visible; -} -.search-popup { - background: var(--card-bg-color); - border-radius: 5px; - height: 80%; - margin: auto; - transform: scale(0); - transition: transform 0.4s; - width: 700px; -} -.search-active .search-popup { - transform: scale(1); -} -@media (max-width: 767px) { - .search-popup { - border-radius: 0; - height: 100%; - width: 100%; - } -} -.search-popup .search-icon, -.search-popup .popup-btn-close { - color: #999; - font-size: 18px; - padding: 0 10px; -} -.search-popup .popup-btn-close { - cursor: pointer; -} -.search-popup .popup-btn-close:hover .fa { - color: #222; -} -.search-popup .search-header { - background: #eee; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - display: flex; - padding: 5px; -} -@media (prefers-color-scheme: dark) { - .search-popup .search-header { - background: #666; - } -} -.search-popup input.search-input { - background: transparent; - border: 0; - outline: 0; - width: 100%; -} -.search-popup input.search-input::-webkit-search-cancel-button { - display: none; -} -.search-popup .search-result-container { - height: calc(100% - 55px); - overflow: auto; - padding: 5px 25px; -} -.search-popup .search-result-container hr { - margin: 5px 0 10px; -} -.search-popup .search-result-container hr:first-child { - display: none; -} -.search-popup .search-result-list { - margin: 0 5px; - padding: 0; -} -.search-popup a.search-result-title { - font-weight: bold; -} -.search-popup p.search-result { - border-bottom: 1px dashed #ccc; - padding: 5px 0; -} -.search-popup .search-input-container { - flex-grow: 1; - padding: 2px; -} -.search-popup .no-result { - display: flex; -} -.search-popup .search-result-list { - width: 100%; -} -.search-popup .search-result-icon { - color: #ccc; - margin: auto; -} -mark.search-keyword { - background: transparent; - border-bottom: 1px dashed #ff2a2a; - color: #ff2a2a; - font-weight: bold; -} -.use-motion .animated { - animation-fill-mode: none; - visibility: inherit; -} -.use-motion .sidebar .animated { - animation-fill-mode: both; -} -hr { - height: 2px; - margin: 20px 0; -} -.btn { - padding: 0 10px; -} -.headband { - display: none; -} -@media (max-width: 767px) { - .pagination { - margin: 80px 0 0; - text-align: center; - } -} -.footer { - background: var(--content-bg-color); - color: var(--text-color); - padding: 10px 0; -} -@media (max-width: 767px) { - .footer-inner { - text-align: center; - } -} -.column { - background: var(--content-bg-color); -} -header.header { - align-items: center; - display: flex; - padding: 20px 0; -} -@media (max-width: 767px) { - header.header { - display: block; - padding: 10px 0; - } -} -.site-meta { - line-height: normal; -} -@media (max-width: 767px) { - .site-meta .brand { - display: block; - } -} -.site-meta .site-title { - font-weight: bolder; -} -.logo-line { - background: var(--brand-color); - display: block; - height: 2px; - margin: 0 auto; - width: 75%; -} -@media (max-width: 767px) { - .logo-line { - display: none; - } -} -.use-motion .logo-line:first-of-type { - transform: scaleX(0); - transform-origin: left; -} -.use-motion .logo-line:last-of-type { - transform: scaleX(0); - transform-origin: right; -} -.site-subtitle { - display: none; -} -.site-nav { - flex-grow: 1; -} -@media (max-width: 767px) { - .site-nav { - padding: 0 10px 0; - } -} -@media (max-width: 767px) { - .main-menu { - padding-top: 10px; - } -} -.menu { - padding: 0; -} -.menu .menu-item { - margin: 0; -} -@media (max-width: 767px) { - .menu .menu-item { - margin-top: 5px; - } -} -.menu .menu-item a { - border-radius: 2px; - padding: 0 10px; - transition-property: background; -} -@media (max-width: 767px) { - .menu .menu-item a { - text-align: left; - display: flex; - align-items: center; - } - .menu .menu-item a .badge { - margin-left: auto; - } -} -.posts-expand.index .post-header { - text-align: left; -} -@media (max-width: 767px) { - .posts-expand.index .post-header { - text-align: center; - } -} -.posts-expand.index .post-meta-container { - margin-top: 5px; -} -.posts-expand.index .post-meta { - justify-content: flex-start; -} -@media (max-width: 767px) { - .posts-expand.index .post-meta { - justify-content: center; - } -} -.posts-expand .post-eof { - display: none; -} -.posts-expand .post-block:not(:first-of-type) { - margin-top: 120px; -} -.posts-expand .post-header { - margin-bottom: 20px; -} -.posts-expand .post-tags a { - background: var(--content-bg-color); - border-bottom: 0; - padding: 1px 5px; -} -.posts-expand .post-tags a:hover { - background: var(--menu-item-bg-color); -} -.posts-expand .post-nav { - margin-top: 40px; -} -.post-button { - margin-top: 20px; -} -.post-button .btn { - background: none; - border: 0; - border-bottom: 2px solid var(--btn-default-border-color); - padding: 0; - transition-property: border; -} -.post-button .btn:hover { - border-bottom-color: var(--btn-default-hover-border-color); -} -header.header { - margin: 0 auto; - width: 700px; -} -@media (max-width: 767px) { - header.header { - width: auto; - } -} -@media (min-width: 1200px) { - header.header { - width: 800px; - } -} -@media (min-width: 1600px) { - header.header { - width: 900px; - } -} -.main-inner { - margin: 0 auto; - width: 700px; - padding-bottom: 80px; -} -@media (max-width: 767px) { - .main-inner { - width: auto; - } -} -@media (min-width: 1200px) { - .main-inner { - width: 800px; - } -} -@media (min-width: 1600px) { - .main-inner { - width: 900px; - } -} -@media (max-width: 767px) { - .main-inner { - padding-left: 20px; - padding-right: 20px; - } -} -.post-block:first-of-type { - padding-top: 80px; -} -@media (max-width: 767px) { - .post-block:first-of-type { - padding-top: 60px; - } -} -@media (min-width: 1200px) { - .sidebar-dimmer { - display: none; - } - .sidebar-active { - padding-right: 320px; - } - .sidebar-active .footer-fixed { - right: 320px; - } -} -.sub-menu { - margin: 10px 0; -} -.sub-menu .menu-item { - display: inline-block; -} diff --git a/css/noscript.css b/css/noscript.css deleted file mode 100644 index 6418c57d4..000000000 --- a/css/noscript.css +++ /dev/null @@ -1,48 +0,0 @@ -body { - margin-top: 2rem; -} -.use-motion .menu-item, -.use-motion .sidebar, -.use-motion .sidebar-inner, -.use-motion .post-block, -.use-motion .pagination, -.use-motion .comments, -.use-motion .post-header, -.use-motion .post-body, -.use-motion .collection-header { - visibility: visible; -} -.use-motion .column, -.use-motion .site-brand-container .toggle, -.use-motion .footer { - opacity: initial; -} -.use-motion .site-title, -.use-motion .site-subtitle, -.use-motion .custom-logo-image { - opacity: initial; - top: initial; -} -.use-motion .logo-line { - transform: scaleX(1); -} -.search-pop-overlay, -.sidebar-nav { - display: none; -} -.sidebar-panel { - display: block; -} -.noscript-warning { - background-color: #f55; - color: #fff; - font-family: sans-serif; - font-size: 1rem; - font-weight: bold; - left: 0; - position: fixed; - text-align: center; - top: 0; - width: 100%; - z-index: 50; -} diff --git a/images/apple-touch-icon-next.png b/images/apple-touch-icon-next.png deleted file mode 100644 index 86a0d1d33..000000000 Binary files a/images/apple-touch-icon-next.png and /dev/null differ diff --git a/images/avatar.gif b/images/avatar.gif deleted file mode 100644 index 3b5d744b1..000000000 Binary files a/images/avatar.gif and /dev/null differ diff --git a/images/favicon-16x16-next.png b/images/favicon-16x16-next.png deleted file mode 100644 index de8c5d3a5..000000000 Binary files a/images/favicon-16x16-next.png and /dev/null differ diff --git a/images/favicon-32x32-next.png b/images/favicon-32x32-next.png deleted file mode 100644 index e02f5f4d5..000000000 Binary files a/images/favicon-32x32-next.png and /dev/null differ diff --git a/images/logo.svg b/images/logo.svg deleted file mode 100644 index 992c1a581..000000000 --- a/images/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index 03e9342dd..000000000 --- a/index.html +++ /dev/null @@ -1,1375 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- - - - -

- - -
- - - - -
-

距离上一次更新文章已经过去了 1 年多了,时间可过的真快。原计划将现在的主站点要进行按照领域划分,将平时工作中遇到的、实践的技术整理到对应的领域,方便快速查找,提供一个沉浸式的学习知识体验,但由于个人也是一个懒癌拖更患者,加之在过去的一年工作中处在新领域,学习了很多技术,很多笔记还没有整理完善,因此没有及时更新博客,同时最近也遇到了站点无法正常访问,经过一系列的排查,发现了是由于开源的 CDN 提供方 jsdelivr 被污染了 DNS,因此不得不先更新站点的基础服务,以便能正常访问

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

verified-commit

-

通常 Push 代码到远程托管平台(GitHub,Gitlab,Gitee 等),需要提前在托管平台上传我们 Git 账户的公钥(*.pub),平台使用上传的公钥来验证身份(本地的 Git 私钥与平台上的公钥配对,以确保你有权限读写该仓库),该验证只会在 Push 时进行检查

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

就像文章标题所述。每到三,五月这个时间,网络变得异常脆弱。各种“奇怪”的网站访问起来很费劲。对于一个技术人员,这些问题可以解决,但是每次都需要花费一定的时间和精力来应对这些,而且随着 GFW 的不断升级和加强,应对的策略和技术也是在不断迭代,前前后后已经出现了多种技术,本篇就以这个契机,梳理截止到 2021-05-05 所了解到关于 “代理” 相关的知识。现在主流的科学上网技术有 VPN、SS、SSR、V2Ray、Trojan、Trojan-Go,小众的 WireGuard、Brook、Snell 和 NaiveProxy 等

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在之前 SpringBoot(十二)文件上传 文章中,已经学习了使用 SpringBoot 基础的功能,完成静态资源的管理,本片文章我们同样也是对非结构化的静态数据进行管理,不过这次我们使用的是比较常用的 OSS 服务,废话不说,我们一起开始 OSS 之旅吧

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

MinIO 是一个基于 Apache License v2.0 开源协议使用 Go 语言开发的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在金庸武侠《天龙八部》中,“琅嬛福地”存放了无崖子和李秋水搜罗天下各门各派的武功,江湖人士练成这里的一门武功绝学,就能在江湖中有自己的一席之地。而这里存放了我计算机相关学习、实践应用,以及经常使用的一些网站资源

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

过年正好时间比较集中,可以把之前的一个想法付诸实践,之前一直想给老爸做一个类似于账单管理的应用,方便他每天把客户需要物品记录成一个清单进行管理,其中主要包含已下功能点。其一,支持添加任务列表(账单);其二,支持任务列表分享(账单)。以上是我的第一期规划功能规划,话不多说我们就一起来跟着我来完成这个小程序的开发吧,本篇主要讲小程序的初始化相关工作

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

未曾想过,居然能写到第 100 篇文章。虽然大部分文章都是线性流水操作,但全部是自己经过实践的总结;虽然没有精彩的故事,但都是自己成长的思考;虽然有时一篇文章需要要长达一个多月的反复核对,但还是能默默坚持。只是这第 100 篇来的有点晚,断断续续大概有 3 年的时间,时间是个坏老头,把我给你写情话,悄悄的改成了谎话!

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

前两天刚刚学习了 Gradle 构建 SpringBoot 项目,再查看官方文档时,得知 SpringBoot 从 Spring Boot 2.3.0.M1 版本开始完全切换到使用 Gradle 来构建项目,那么本篇文章就来实践,基于源码来编译构建 SpringBoot,话不多说,本次构建构建是 2020 年的最后一次发布的版本 2.4.1

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

Gradle(一)基础 的文章中,我们已经对 Gradle 有了一定的认识,本篇来看一看在后端开发中使用 Gradle 构建 SpringBoot 项目的开发。通常有两种方式来构建项目,第一种:每个功能模块即是一个代码工程,用一个 Git 仓库来管理,每个模块只负责完成一件事情;第二种:整个系统的多个模块聚合在一个代码工程里面,也就是我们常说的多模块项目,本篇先来讲一讲单工程

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/js/comments.js b/js/comments.js deleted file mode 100644 index 4045e8c06..000000000 --- a/js/comments.js +++ /dev/null @@ -1,21 +0,0 @@ -/* global CONFIG */ - -window.addEventListener('tabs:register', () => { - let { activeClass } = CONFIG.comments; - if (CONFIG.comments.storage) { - activeClass = localStorage.getItem('comments_active') || activeClass; - } - if (activeClass) { - const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`); - if (activeTab) { - activeTab.click(); - } - } -}); -if (CONFIG.comments.storage) { - window.addEventListener('tabs:click', event => { - if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; - const commentClass = event.target.classList[1]; - localStorage.setItem('comments_active', commentClass); - }); -} diff --git a/js/config.js b/js/config.js deleted file mode 100644 index caa0075b1..000000000 --- a/js/config.js +++ /dev/null @@ -1,66 +0,0 @@ -if (!window.NexT) window.NexT = {}; - -(function() { - const className = 'next-config'; - - const staticConfig = {}; - let variableConfig = {}; - - const parse = text => JSON.parse(text || '{}'); - - const update = name => { - const targetEle = document.querySelector(`.${className}[data-name="${name}"]`); - if (!targetEle) return; - const parsedConfig = parse(targetEle.text); - if (name === 'main') { - Object.assign(staticConfig, parsedConfig); - } else { - variableConfig[name] = parsedConfig; - } - }; - - update('main'); - - window.CONFIG = new Proxy({}, { - get(overrideConfig, name) { - let existing; - if (name in staticConfig) { - existing = staticConfig[name]; - } else { - if (!(name in variableConfig)) update(name); - existing = variableConfig[name]; - } - - // For unset override and mixable existing - if (!(name in overrideConfig) && typeof existing === 'object') { - // Get ready to mix. - overrideConfig[name] = {}; - } - - if (name in overrideConfig) { - const override = overrideConfig[name]; - - // When mixable - if (typeof override === 'object' && typeof existing === 'object') { - // Mix, proxy changes to the override. - return new Proxy({ ...existing, ...override }, { - set(target, prop, value) { - target[prop] = value; - override[prop] = value; - return true; - } - }); - } - - return override; - } - - // Only when not mixable and override hasn't been set. - return existing; - } - }); - - document.addEventListener('pjax:success', () => { - variableConfig = {}; - }); -})(); diff --git a/js/motion.js b/js/motion.js deleted file mode 100644 index fb4669d45..000000000 --- a/js/motion.js +++ /dev/null @@ -1,140 +0,0 @@ -/* global NexT, CONFIG */ - -NexT.motion = {}; - -NexT.motion.integrator = { - queue: [], - init : function() { - this.queue = []; - return this; - }, - add: function(fn) { - const sequence = fn(); - if (CONFIG.motion.async) this.queue.push(sequence); - else this.queue = this.queue.concat(sequence); - return this; - }, - bootstrap: function() { - if (!CONFIG.motion.async) this.queue = [this.queue]; - this.queue.forEach(sequence => { - const timeline = window.anime.timeline({ - duration: 200, - easing : 'linear' - }); - sequence.forEach(item => { - if (item.deltaT) timeline.add(item, item.deltaT); - else timeline.add(item); - }); - }); - } -}; - -NexT.motion.middleWares = { - header: function() { - const sequence = []; - - function getMistLineSettings(targets) { - sequence.push({ - targets, - scaleX : [0, 1], - duration: 500, - deltaT : '-=200' - }); - } - - function pushToSequence(targets, sequenceQueue = false) { - sequence.push({ - targets, - opacity: 1, - top : 0, - deltaT : sequenceQueue ? '-=200' : '-=0' - }); - } - - pushToSequence('.column'); - CONFIG.scheme === 'Mist' && getMistLineSettings('.logo-line'); - CONFIG.scheme === 'Muse' && pushToSequence('.custom-logo-image'); - pushToSequence('.site-title'); - pushToSequence('.site-brand-container .toggle', true); - pushToSequence('.site-subtitle'); - (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') && pushToSequence('.custom-logo-image'); - - const menuItemTransition = CONFIG.motion.transition.menu_item; - if (menuItemTransition) { - document.querySelectorAll('.menu-item').forEach(targets => { - sequence.push({ - targets, - complete: () => targets.classList.add('animated', menuItemTransition), - deltaT : '-=200' - }); - }); - } - - return sequence; - }, - - subMenu: function() { - const subMenuItem = document.querySelectorAll('.sub-menu .menu-item'); - if (subMenuItem.length > 0) { - subMenuItem.forEach(element => { - element.classList.add('animated'); - }); - } - return []; - }, - - postList: function() { - const sequence = []; - const { post_block, post_header, post_body, coll_header } = CONFIG.motion.transition; - - function animate(animation, elements) { - if (!animation) return; - elements.forEach(targets => { - sequence.push({ - targets, - complete: () => targets.classList.add('animated', animation), - deltaT : '-=100' - }); - }); - } - - document.querySelectorAll('.post-block').forEach(targets => { - sequence.push({ - targets, - complete: () => targets.classList.add('animated', post_block), - deltaT : '-=100' - }); - animate(coll_header, targets.querySelectorAll('.collection-header')); - animate(post_header, targets.querySelectorAll('.post-header')); - animate(post_body, targets.querySelectorAll('.post-body')); - }); - - animate(post_block, document.querySelectorAll('.pagination, .comments')); - - return sequence; - }, - - sidebar: function() { - const sequence = []; - const sidebar = document.querySelectorAll('.sidebar-inner'); - const sidebarTransition = CONFIG.motion.transition.sidebar; - // Only for desktop of Pisces | Gemini. - if (sidebarTransition && (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') && window.innerWidth >= 992) { - sidebar.forEach(targets => { - sequence.push({ - targets, - complete: () => targets.classList.add('animated', sidebarTransition), - deltaT : '-=100' - }); - }); - } - return sequence; - }, - - footer: function() { - return [{ - targets: document.querySelector('.footer'), - opacity: 1 - }]; - } -}; diff --git a/js/next-boot.js b/js/next-boot.js deleted file mode 100644 index e732f844a..000000000 --- a/js/next-boot.js +++ /dev/null @@ -1,80 +0,0 @@ -/* global NexT, CONFIG */ - -NexT.boot = {}; - -NexT.boot.registerEvents = function() { - - NexT.utils.registerScrollPercent(); - NexT.utils.registerCanIUseTag(); - NexT.utils.updateFooterPosition(); - - // Mobile top menu bar. - document.querySelector('.site-nav-toggle .toggle').addEventListener('click', event => { - event.currentTarget.classList.toggle('toggle-close'); - const siteNav = document.querySelector('.site-nav'); - if (!siteNav) return; - siteNav.style.setProperty('--scroll-height', siteNav.scrollHeight + 'px'); - document.body.classList.toggle('site-nav-on'); - }); - - document.querySelectorAll('.sidebar-nav li').forEach((element, index) => { - element.addEventListener('click', () => { - NexT.utils.activateSidebarPanel(index); - }); - }); - - window.addEventListener('hashchange', () => { - const tHash = location.hash; - if (tHash !== '' && !tHash.match(/%\S{2}/)) { - const target = document.querySelector(`.tabs ul.nav-tabs li a[href="${tHash}"]`); - target && target.click(); - } - }); - - window.addEventListener('tabs:click', e => { - NexT.utils.registerCodeblock(e.target); - }); -}; - -NexT.boot.refresh = function() { - - /** - * Register JS handlers by condition option. - * Need to add config option in Front-End at 'scripts/helpers/next-config.js' file. - */ - CONFIG.prism && window.Prism.highlightAll(); - CONFIG.mediumzoom && window.mediumZoom('.post-body :not(a) > img, .post-body > img', { - background: 'var(--content-bg-color)' - }); - CONFIG.lazyload && window.lozad('.post-body img').observe(); - CONFIG.pangu && window.pangu.spacingPage(); - - CONFIG.exturl && NexT.utils.registerExtURL(); - NexT.utils.wrapTableWithBox(); - NexT.utils.registerCodeblock(); - NexT.utils.registerTabsTag(); - NexT.utils.registerActiveMenuItem(); - NexT.utils.registerLangSelect(); - NexT.utils.registerSidebarTOC(); - NexT.utils.registerPostReward(); - NexT.utils.registerVideoIframe(); -}; - -NexT.boot.motion = function() { - // Define Motion Sequence & Bootstrap Motion. - if (CONFIG.motion.enable) { - NexT.motion.integrator - .add(NexT.motion.middleWares.header) - .add(NexT.motion.middleWares.sidebar) - .add(NexT.motion.middleWares.postList) - .add(NexT.motion.middleWares.footer) - .bootstrap(); - } - NexT.utils.updateSidebarPosition(); -}; - -document.addEventListener('DOMContentLoaded', () => { - NexT.boot.registerEvents(); - NexT.boot.refresh(); - NexT.boot.motion(); -}); diff --git a/js/sidebar.js b/js/sidebar.js deleted file mode 100644 index 6ab3c6b05..000000000 --- a/js/sidebar.js +++ /dev/null @@ -1,50 +0,0 @@ -/* global CONFIG */ - -document.addEventListener('DOMContentLoaded', () => { - - const isRight = CONFIG.sidebar.position === 'right'; - - const sidebarToggleMotion = { - mouse: {}, - init : function() { - window.addEventListener('mousedown', this.mousedownHandler.bind(this)); - window.addEventListener('mouseup', this.mouseupHandler.bind(this)); - document.querySelector('.sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this)); - document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this)); - window.addEventListener('sidebar:show', this.showSidebar); - window.addEventListener('sidebar:hide', this.hideSidebar); - }, - mousedownHandler: function(event) { - this.mouse.X = event.pageX; - this.mouse.Y = event.pageY; - }, - mouseupHandler: function(event) { - const deltaX = event.pageX - this.mouse.X; - const deltaY = event.pageY - this.mouse.Y; - const clickingBlankPart = Math.hypot(deltaX, deltaY) < 20 && event.target.matches('.main'); - // Fancybox has z-index property, but medium-zoom does not, so the sidebar will overlay the zoomed image. - if (clickingBlankPart || event.target.matches('img.medium-zoom-image')) { - this.hideSidebar(); - } - }, - clickHandler: function() { - document.body.classList.contains('sidebar-active') ? this.hideSidebar() : this.showSidebar(); - }, - showSidebar: function() { - document.body.classList.add('sidebar-active'); - const animateAction = isRight ? 'fadeInRight' : 'fadeInLeft'; - document.querySelectorAll('.sidebar .animated').forEach((element, index) => { - element.style.animationDelay = (100 * index) + 'ms'; - element.classList.remove(animateAction); - setTimeout(() => { - // Trigger a DOM reflow - element.classList.add(animateAction); - }); - }); - }, - hideSidebar: function() { - document.body.classList.remove('sidebar-active'); - } - }; - sidebarToggleMotion.init(); -}); diff --git a/js/third-party/addtoany.js b/js/third-party/addtoany.js deleted file mode 100644 index f9009f87b..000000000 --- a/js/third-party/addtoany.js +++ /dev/null @@ -1,8 +0,0 @@ -/* global NexT */ - -document.addEventListener('page:loaded', () => { - NexT.utils.getScript('https://static.addtoany.com/menu/page.js', { condition: window.a2a }) - .then(() => { - window.a2a.init(); - }); -}); diff --git a/js/third-party/analytics/google-analytics.js b/js/third-party/analytics/google-analytics.js deleted file mode 100644 index 8601806e0..000000000 --- a/js/third-party/analytics/google-analytics.js +++ /dev/null @@ -1,53 +0,0 @@ -/* global CONFIG, dataLayer, gtag */ - -if (!CONFIG.google_analytics.only_pageview) { - if (CONFIG.hostname === location.hostname) { - window.dataLayer = window.dataLayer || []; - window.gtag = function() { - dataLayer.push(arguments); - }; - gtag('js', new Date()); - gtag('config', CONFIG.google_analytics.tracking_id); - - document.addEventListener('pjax:success', () => { - gtag('event', 'page_view', { - page_location: location.href, - page_path : location.pathname, - page_title : document.title - }); - }); - } -} else { - const sendPageView = () => { - if (CONFIG.hostname !== location.hostname) return; - const uid = localStorage.getItem('uid') || (Math.random() + '.' + Math.random()); - localStorage.setItem('uid', uid); - fetch( - 'https://www.google-analytics.com/mp/collect?' + new URLSearchParams({ - api_secret : CONFIG.google_analytics.measure_protocol_api_secret, - measurement_id: CONFIG.google_analytics.tracking_id - }), - { - method : 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - client_id: uid, - events : [ - { - name : 'page_view', - params: { - page_location: location.href, - page_title : document.title - } - } - ] - }), - mode: 'no-cors' - } - ); - }; - document.addEventListener('pjax:complete', sendPageView); - sendPageView(); -} diff --git a/js/third-party/analytics/matomo.js b/js/third-party/analytics/matomo.js deleted file mode 100644 index 290a3e091..000000000 --- a/js/third-party/analytics/matomo.js +++ /dev/null @@ -1,19 +0,0 @@ -/* global CONFIG */ - -if (CONFIG.matomo.enable) { - window._paq = window._paq || []; - const _paq = window._paq; - - /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ - _paq.push(['trackPageView']); - _paq.push(['enableLinkTracking']); - const u = CONFIG.matomo.server_url; - _paq.push(['setTrackerUrl', u + 'matomo.php']); - _paq.push(['setSiteId', CONFIG.matomo.site_id]); - const d = document; - const g = d.createElement('script'); - const s = d.getElementsByTagName('script')[0]; - g.async = true; - g.src = u + 'matomo.js'; - s.parentNode.insertBefore(g, s); -} diff --git a/js/third-party/comments/utterances.js b/js/third-party/comments/utterances.js deleted file mode 100644 index 332ee0577..000000000 --- a/js/third-party/comments/utterances.js +++ /dev/null @@ -1,17 +0,0 @@ -/* global NexT, CONFIG */ - -document.addEventListener('page:loaded', () => { - if (!CONFIG.page.comments) return; - - NexT.utils.loadComments('.utterances-container') - .then(() => NexT.utils.getScript('https://utteranc.es/client.js', { - attributes: { - async : true, - crossOrigin : 'anonymous', - 'repo' : CONFIG.utterances.repo, - 'issue-term': CONFIG.utterances.issue_term, - 'theme' : CONFIG.utterances.theme - }, - parentNode: document.querySelector('.utterances-container') - })); -}); diff --git a/js/third-party/fancybox.js b/js/third-party/fancybox.js deleted file mode 100644 index 178db4b1e..000000000 --- a/js/third-party/fancybox.js +++ /dev/null @@ -1,35 +0,0 @@ -/* global Fancybox */ - -document.addEventListener('page:loaded', () => { - - /** - * Wrap images with fancybox. - */ - document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(image => { - const imageLink = image.dataset.src || image.src; - const imageWrapLink = document.createElement('a'); - imageWrapLink.classList.add('fancybox'); - imageWrapLink.href = imageLink; - imageWrapLink.setAttribute('itemscope', ''); - imageWrapLink.setAttribute('itemtype', 'http://schema.org/ImageObject'); - imageWrapLink.setAttribute('itemprop', 'url'); - - let dataFancybox = 'default'; - if (image.closest('.post-gallery') !== null) { - dataFancybox = 'gallery'; - } else if (image.closest('.group-picture') !== null) { - dataFancybox = 'group'; - } - imageWrapLink.dataset.fancybox = dataFancybox; - - const imageTitle = image.title || image.alt; - if (imageTitle) { - imageWrapLink.title = imageTitle; - // Make sure img captions will show correctly in fancybox - imageWrapLink.dataset.caption = imageTitle; - } - image.wrap(imageWrapLink); - }); - - Fancybox.bind('[data-fancybox]'); -}); diff --git a/js/third-party/pace.js b/js/third-party/pace.js deleted file mode 100644 index c22d59f01..000000000 --- a/js/third-party/pace.js +++ /dev/null @@ -1,7 +0,0 @@ -/* global Pace */ - -Pace.options.restartOnPushState = false; - -document.addEventListener('pjax:send', () => { - Pace.restart(); -}); diff --git a/js/third-party/search/local-search.js b/js/third-party/search/local-search.js deleted file mode 100644 index 92a264dc9..000000000 --- a/js/third-party/search/local-search.js +++ /dev/null @@ -1,99 +0,0 @@ -/* global CONFIG, pjax, LocalSearch */ - -document.addEventListener('DOMContentLoaded', () => { - if (!CONFIG.path) { - // Search DB path - console.warn('`hexo-generator-searchdb` plugin is not installed!'); - return; - } - const localSearch = new LocalSearch({ - path : CONFIG.path, - top_n_per_article: CONFIG.localsearch.top_n_per_article, - unescape : CONFIG.localsearch.unescape - }); - - const input = document.querySelector('.search-input'); - - const inputEventFunction = () => { - if (!localSearch.isfetched) return; - const searchText = input.value.trim().toLowerCase(); - const keywords = searchText.split(/[-\s]+/); - const container = document.querySelector('.search-result-container'); - let resultItems = []; - if (searchText.length > 0) { - // Perform local searching - resultItems = localSearch.getResultItems(keywords); - } - if (keywords.length === 1 && keywords[0] === '') { - container.classList.add('no-result'); - container.innerHTML = '
'; - } else if (resultItems.length === 0) { - container.classList.add('no-result'); - container.innerHTML = '
'; - } else { - resultItems.sort((left, right) => { - if (left.includedCount !== right.includedCount) { - return right.includedCount - left.includedCount; - } else if (left.hitCount !== right.hitCount) { - return right.hitCount - left.hitCount; - } - return right.id - left.id; - }); - const stats = CONFIG.i18n.hits.replace('${hits}', resultItems.length); - - container.classList.remove('no-result'); - container.innerHTML = `
${stats}
-
-
    ${resultItems.map(result => result.item).join('')}
`; - if (typeof pjax === 'object') pjax.refresh(container); - } - }; - - localSearch.highlightSearchWords(document.querySelector('.post-body')); - if (CONFIG.localsearch.preload) { - localSearch.fetchData(); - } - - if (CONFIG.localsearch.trigger === 'auto') { - input.addEventListener('input', inputEventFunction); - } else { - document.querySelector('.search-icon').addEventListener('click', inputEventFunction); - input.addEventListener('keypress', event => { - if (event.key === 'Enter') { - inputEventFunction(); - } - }); - } - window.addEventListener('search:loaded', inputEventFunction); - - // Handle and trigger popup window - document.querySelectorAll('.popup-trigger').forEach(element => { - element.addEventListener('click', () => { - document.body.classList.add('search-active'); - // Wait for search-popup animation to complete - setTimeout(() => input.focus(), 500); - if (!localSearch.isfetched) localSearch.fetchData(); - }); - }); - - // Monitor main search box - const onPopupClose = () => { - document.body.classList.remove('search-active'); - }; - - document.querySelector('.search-pop-overlay').addEventListener('click', event => { - if (event.target === document.querySelector('.search-pop-overlay')) { - onPopupClose(); - } - }); - document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); - document.addEventListener('pjax:success', () => { - localSearch.highlightSearchWords(document.querySelector('.post-body')); - onPopupClose(); - }); - window.addEventListener('keyup', event => { - if (event.key === 'Escape') { - onPopupClose(); - } - }); -}); diff --git a/js/third-party/statistics/lean-analytics.js b/js/third-party/statistics/lean-analytics.js deleted file mode 100644 index 8397112b1..000000000 --- a/js/third-party/statistics/lean-analytics.js +++ /dev/null @@ -1,107 +0,0 @@ -/* global CONFIG */ -/* eslint-disable no-console */ - -(function() { - const leancloudSelector = url => { - url = encodeURI(url); - return document.getElementById(url).querySelector('.leancloud-visitors-count'); - }; - - const addCount = Counter => { - const visitors = document.querySelector('.leancloud_visitors'); - const url = decodeURI(visitors.id); - const title = visitors.dataset.flagTitle; - - Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url }))}`) - .then(response => response.json()) - .then(({ results }) => { - if (results.length > 0) { - const counter = results[0]; - leancloudSelector(url).innerText = counter.time + 1; - Counter('put', '/classes/Counter/' + counter.objectId, { - time: { - '__op' : 'Increment', - 'amount': 1 - } - }) - .catch(error => { - console.error('Failed to save visitor count', error); - }); - } else if (CONFIG.leancloud_visitors.security) { - leancloudSelector(url).innerText = 'Counter not initialized! More info at console err msg.'; - console.error('ATTENTION! LeanCloud counter has security bug, see how to solve it here: https://github.com/theme-next/hexo-leancloud-counter-security. \n However, you can still use LeanCloud without security, by setting `security` option to `false`.'); - } else { - Counter('post', '/classes/Counter', { title, url, time: 1 }) - .then(response => response.json()) - .then(() => { - leancloudSelector(url).innerText = 1; - }) - .catch(error => { - console.error('Failed to create', error); - }); - } - }) - .catch(error => { - console.error('LeanCloud Counter Error', error); - }); - }; - - const showTime = Counter => { - const visitors = document.querySelectorAll('.leancloud_visitors'); - const entries = [...visitors].map(element => { - return decodeURI(element.id); - }); - - Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url: { '$in': entries } }))}`) - .then(response => response.json()) - .then(({ results }) => { - for (const url of entries) { - const target = results.find(item => item.url === url); - leancloudSelector(url).innerText = target ? target.time : 0; - } - }) - .catch(error => { - console.error('LeanCloud Counter Error', error); - }); - }; - - const { app_id, app_key, server_url } = CONFIG.leancloud_visitors; - const fetchData = api_server => { - const Counter = (method, url, data) => { - return fetch(`${api_server}/1.1${url}`, { - method, - headers: { - 'X-LC-Id' : app_id, - 'X-LC-Key' : app_key, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }); - }; - if (CONFIG.page.isPost) { - if (CONFIG.hostname !== location.hostname) return; - addCount(Counter); - } else if (document.querySelectorAll('.post-title-link').length >= 1) { - showTime(Counter); - } - }; - - let api_server; - if (server_url) { - api_server = server_url; - } else if (app_id.slice(-9) === '-MdYXbMMI') { - api_server = `https://${app_id.slice(0, 8).toLowerCase()}.api.lncldglobal.com`; - } - - document.addEventListener('page:loaded', () => { - if (api_server) { - fetchData(api_server); - } else { - fetch(`https://app-router.leancloud.cn/2/route?appId=${app_id}`) - .then(response => response.json()) - .then(({ api_server }) => { - fetchData(`https://${api_server}`); - }); - } - }); -})(); diff --git a/js/third-party/tags/mermaid.js b/js/third-party/tags/mermaid.js deleted file mode 100644 index 54f62885e..000000000 --- a/js/third-party/tags/mermaid.js +++ /dev/null @@ -1,32 +0,0 @@ -/* global NexT, CONFIG, mermaid */ - -document.addEventListener('page:loaded', () => { - const mermaidElements = document.querySelectorAll('.mermaid'); - if (mermaidElements.length) { - NexT.utils.getScript(CONFIG.mermaid.js, { - condition: window.mermaid - }).then(() => { - mermaidElements.forEach(element => { - const newElement = document.createElement('div'); - newElement.innerHTML = element.innerHTML; - newElement.className = element.className; - const parent = element.parentNode; - // Fix issue #347 - // Support mermaid inside backtick code block - if (parent.matches('pre')) { - parent.parentNode.replaceChild(newElement, parent); - } else { - parent.replaceChild(newElement, element); - } - }); - mermaid.initialize({ - theme : CONFIG.darkmode && window.matchMedia('(prefers-color-scheme: dark)').matches ? CONFIG.mermaid.theme.dark : CONFIG.mermaid.theme.light, - logLevel : 4, - flowchart: { curve: 'linear' }, - gantt : { axisFormat: '%m/%d/%Y' }, - sequence : { actorMargin: 50 } - }); - mermaid.run(); - }); - } -}); diff --git a/js/third-party/tags/wavedrom.js b/js/third-party/tags/wavedrom.js deleted file mode 100644 index ddd9a1d97..000000000 --- a/js/third-party/tags/wavedrom.js +++ /dev/null @@ -1,13 +0,0 @@ -/* global NexT, CONFIG, WaveDrom */ - -document.addEventListener('page:loaded', () => { - NexT.utils.getScript(CONFIG.wavedrom.js, { - condition: window.WaveDrom - }).then(() => { - NexT.utils.getScript(CONFIG.wavedrom_skin.js, { - condition: window.WaveSkin - }).then(() => { - WaveDrom.ProcessAll(); - }); - }); -}); diff --git a/js/utils.js b/js/utils.js deleted file mode 100644 index ebd85526a..000000000 --- a/js/utils.js +++ /dev/null @@ -1,502 +0,0 @@ -/* global NexT, CONFIG */ - -HTMLElement.prototype.wrap = function(wrapper) { - this.parentNode.insertBefore(wrapper, this); - this.parentNode.removeChild(this); - wrapper.appendChild(this); -}; - -(function() { - const onPageLoaded = () => document.dispatchEvent( - new Event('page:loaded', { - bubbles: true - }) - ); - - if (document.readyState === 'loading') { - document.addEventListener('readystatechange', onPageLoaded, { once: true }); - } else { - onPageLoaded(); - } - document.addEventListener('pjax:success', onPageLoaded); -})(); - -NexT.utils = { - - registerExtURL: function() { - document.querySelectorAll('span.exturl').forEach(element => { - const link = document.createElement('a'); - // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings - link.href = decodeURIComponent(atob(element.dataset.url).split('').map(c => { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); - link.rel = 'noopener external nofollow noreferrer'; - link.target = '_blank'; - link.className = element.className; - link.title = element.title; - link.innerHTML = element.innerHTML; - element.parentNode.replaceChild(link, element); - }); - }, - - registerCodeblock: function(element) { - const inited = !!element; - let figure = (inited ? element : document).querySelectorAll('figure.highlight'); - let isHljsWithWrap = true; - if (figure.length === 0) { - figure = document.querySelectorAll('pre:not(.mermaid)'); - isHljsWithWrap = false; - } - figure.forEach(element => { - if (!inited) { - let span = element.querySelectorAll('.code .line span'); - if (span.length === 0) { - // Hljs without line_number and wrap - span = element.querySelectorAll('code.highlight span'); - } - span.forEach(s => { - s.classList.forEach(name => { - s.classList.replace(name, `hljs-${name}`); - }); - }); - } - const height = parseInt(window.getComputedStyle(element).height.replace('px', ''), 10); - const needFold = CONFIG.fold.enable && (height > CONFIG.fold.height); - if (!needFold && !CONFIG.copycode.enable) return; - let target; - if (isHljsWithWrap && CONFIG.copycode.style === 'mac') { - target = element; - } else { - let box = element.querySelector('.code-container'); - if (!box) { - // https://github.com/next-theme/hexo-theme-next/issues/98 - // https://github.com/next-theme/hexo-theme-next/pull/508 - const container = element.querySelector('.table-container') || element; - box = document.createElement('div'); - box.className = 'code-container'; - container.wrap(box); - - // add "notranslate" to prevent Google Translate from translating it, which also completely messes up the layout - box.classList.add('notranslate'); - } - target = box; - } - if (needFold && !target.classList.contains('unfold')) { - target.classList.add('highlight-fold'); - target.insertAdjacentHTML('beforeend', '
'); - target.querySelector('.expand-btn').addEventListener('click', () => { - target.classList.remove('highlight-fold'); - target.classList.add('unfold'); - }); - } - if (inited || !CONFIG.copycode.enable) return; - // One-click copy code support. - target.insertAdjacentHTML('beforeend', '
'); - const button = target.querySelector('.copy-btn'); - button.addEventListener('click', () => { - const lines = element.querySelector('.code') || element.querySelector('code'); - const code = lines.innerText; - if (navigator.clipboard) { - // https://caniuse.com/mdn-api_clipboard_writetext - navigator.clipboard.writeText(code).then(() => { - button.querySelector('i').className = 'fa fa-check-circle fa-fw'; - }, () => { - button.querySelector('i').className = 'fa fa-times-circle fa-fw'; - }); - } else { - const ta = document.createElement('textarea'); - ta.style.top = window.scrollY + 'px'; // Prevent page scrolling - ta.style.position = 'absolute'; - ta.style.opacity = '0'; - ta.readOnly = true; - ta.value = code; - document.body.append(ta); - ta.select(); - ta.setSelectionRange(0, code.length); - ta.readOnly = false; - const result = document.execCommand('copy'); - button.querySelector('i').className = result ? 'fa fa-check-circle fa-fw' : 'fa fa-times-circle fa-fw'; - ta.blur(); // For iOS - button.blur(); - document.body.removeChild(ta); - } - }); - element.addEventListener('mouseleave', () => { - setTimeout(() => { - button.querySelector('i').className = 'fa fa-copy fa-fw'; - }, 300); - }); - }); - }, - - wrapTableWithBox: function() { - document.querySelectorAll('table').forEach(element => { - const box = document.createElement('div'); - box.className = 'table-container'; - element.wrap(box); - }); - }, - - registerVideoIframe: function() { - document.querySelectorAll('iframe').forEach(element => { - const supported = [ - 'www.youtube.com', - 'player.vimeo.com', - 'player.youku.com', - 'player.bilibili.com', - 'www.tudou.com' - ].some(host => element.src.includes(host)); - if (supported && !element.parentNode.matches('.video-container')) { - const box = document.createElement('div'); - box.className = 'video-container'; - element.wrap(box); - const width = Number(element.width); - const height = Number(element.height); - if (width && height) { - box.style.paddingTop = (height / width * 100) + '%'; - } - } - }); - }, - - updateActiveNav: function() { - if (!Array.isArray(NexT.utils.sections)) return; - let index = NexT.utils.sections.findIndex(element => { - return element && element.getBoundingClientRect().top > 10; - }); - if (index === -1) { - index = NexT.utils.sections.length - 1; - } else if (index > 0) { - index--; - } - this.activateNavByIndex(index); - }, - - registerScrollPercent: function() { - const backToTop = document.querySelector('.back-to-top'); - const readingProgressBar = document.querySelector('.reading-progress-bar'); - // For init back to top in sidebar if page was scrolled after page refresh. - window.addEventListener('scroll', () => { - if (backToTop || readingProgressBar) { - const contentHeight = document.body.scrollHeight - window.innerHeight; - const scrollPercent = contentHeight > 0 ? Math.min(100 * window.scrollY / contentHeight, 100) : 0; - if (backToTop) { - backToTop.classList.toggle('back-to-top-on', Math.round(scrollPercent) >= 5); - backToTop.querySelector('span').innerText = Math.round(scrollPercent) + '%'; - } - if (readingProgressBar) { - readingProgressBar.style.setProperty('--progress', scrollPercent.toFixed(2) + '%'); - } - } - this.updateActiveNav(); - }, { passive: true }); - - backToTop && backToTop.addEventListener('click', () => { - window.anime({ - targets : document.scrollingElement, - duration : 500, - easing : 'linear', - scrollTop: 0 - }); - }); - }, - - /** - * Tabs tag listener (without twitter bootstrap). - */ - registerTabsTag: function() { - // Binding `nav-tabs` & `tab-content` by real time permalink changing. - document.querySelectorAll('.tabs ul.nav-tabs .tab').forEach(element => { - element.addEventListener('click', event => { - event.preventDefault(); - // Prevent selected tab to select again. - if (element.classList.contains('active')) return; - const nav = element.parentNode; - // Get the height of `tab-pane` which is activated before, and set it as the height of `tab-content` with extra margin / paddings. - const tabContent = nav.nextElementSibling; - tabContent.style.overflow = 'hidden'; - tabContent.style.transition = 'height 1s'; - // Comment system selection tab does not contain .active class. - const activeTab = tabContent.querySelector('.active') || tabContent.firstElementChild; - // Hight might be `auto`. - const prevHeight = parseInt(window.getComputedStyle(activeTab).height.replace('px', ''), 10) || 0; - const paddingTop = parseInt(window.getComputedStyle(activeTab).paddingTop.replace('px', ''), 10); - const marginBottom = parseInt(window.getComputedStyle(activeTab.firstElementChild).marginBottom.replace('px', ''), 10); - tabContent.style.height = prevHeight + paddingTop + marginBottom + 'px'; - // Add & Remove active class on `nav-tabs` & `tab-content`. - [...nav.children].forEach(target => { - target.classList.toggle('active', target === element); - }); - // https://stackoverflow.com/questions/20306204/using-queryselector-with-ids-that-are-numbers - const tActive = document.getElementById(element.querySelector('a').getAttribute('href').replace('#', '')); - [...tActive.parentNode.children].forEach(target => { - target.classList.toggle('active', target === tActive); - }); - // Trigger event - tActive.dispatchEvent(new Event('tabs:click', { - bubbles: true - })); - // Get the height of `tab-pane` which is activated now. - const hasScrollBar = document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight); - const currHeight = parseInt(window.getComputedStyle(tabContent.querySelector('.active')).height.replace('px', ''), 10); - // Reset the height of `tab-content` and see the animation. - tabContent.style.height = currHeight + paddingTop + marginBottom + 'px'; - // Change the height of `tab-content` may cause scrollbar show / disappear, which may result in the change of the `tab-pane`'s height - setTimeout(() => { - if ((document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight)) !== hasScrollBar) { - tabContent.style.transition = 'height 0.3s linear'; - // After the animation, we need reset the height of `tab-content` again. - const currHeightAfterScrollBarChange = parseInt(window.getComputedStyle(tabContent.querySelector('.active')).height.replace('px', ''), 10); - tabContent.style.height = currHeightAfterScrollBarChange + paddingTop + marginBottom + 'px'; - } - // Remove all the inline styles, and let the height be adaptive again. - setTimeout(() => { - tabContent.style.transition = ''; - tabContent.style.height = ''; - }, 250); - }, 1000); - if (!CONFIG.stickytabs) return; - const offset = nav.parentNode.getBoundingClientRect().top + window.scrollY + 10; - window.anime({ - targets : document.scrollingElement, - duration : 500, - easing : 'linear', - scrollTop: offset - }); - }); - }); - - window.dispatchEvent(new Event('tabs:register')); - }, - - registerCanIUseTag: function() { - // Get responsive height passed from iframe. - window.addEventListener('message', ({ data }) => { - if (typeof data === 'string' && data.includes('ciu_embed')) { - const featureID = data.split(':')[1]; - const height = data.split(':')[2]; - document.querySelector(`iframe[data-feature=${featureID}]`).style.height = parseInt(height, 10) + 5 + 'px'; - } - }, false); - }, - - registerActiveMenuItem: function() { - document.querySelectorAll('.menu-item a[href]').forEach(target => { - const isSamePath = target.pathname === location.pathname || target.pathname === location.pathname.replace('index.html', ''); - const isSubPath = !CONFIG.root.startsWith(target.pathname) && location.pathname.startsWith(target.pathname); - target.classList.toggle('menu-item-active', target.hostname === location.hostname && (isSamePath || isSubPath)); - }); - }, - - registerLangSelect: function() { - const selects = document.querySelectorAll('.lang-select'); - selects.forEach(sel => { - sel.value = CONFIG.page.lang; - sel.addEventListener('change', () => { - const target = sel.options[sel.selectedIndex]; - document.querySelectorAll('.lang-select-label span').forEach(span => { - span.innerText = target.text; - }); - // Disable Pjax to force refresh translation of menu item - window.location.href = target.dataset.href; - }); - }); - }, - - registerSidebarTOC: function() { - this.sections = [...document.querySelectorAll('.post-toc:not(.placeholder-toc) li a.nav-link')].map(element => { - const target = document.getElementById(decodeURI(element.getAttribute('href')).replace('#', '')); - // TOC item animation navigate. - element.addEventListener('click', event => { - event.preventDefault(); - const offset = target.getBoundingClientRect().top + window.scrollY; - window.anime({ - targets : document.scrollingElement, - duration : 500, - easing : 'linear', - scrollTop: offset, - complete : () => { - history.pushState(null, document.title, element.href); - } - }); - }); - return target; - }); - this.updateActiveNav(); - }, - - registerPostReward: function() { - const button = document.querySelector('.reward-container button'); - if (!button) return; - button.addEventListener('click', () => { - document.querySelector('.post-reward').classList.toggle('active'); - }); - }, - - activateNavByIndex: function(index) { - const nav = document.querySelector('.post-toc:not(.placeholder-toc) .nav'); - if (!nav) return; - - const navItemList = nav.querySelectorAll('.nav-item'); - const target = navItemList[index]; - if (!target || target.classList.contains('active-current')) return; - - const singleHeight = navItemList[navItemList.length - 1].offsetHeight; - - nav.querySelectorAll('.active').forEach(navItem => { - navItem.classList.remove('active', 'active-current'); - }); - target.classList.add('active', 'active-current'); - - let activateEle = target.querySelector('.nav-child') || target.parentElement; - let navChildHeight = 0; - - while (nav.contains(activateEle)) { - if (activateEle.classList.contains('nav-item')) { - activateEle.classList.add('active'); - } else { // .nav-child or .nav - // scrollHeight isn't reliable for transitioning child items. - // The last nav-item in a list has a margin-bottom of 5px. - navChildHeight += (singleHeight * activateEle.childElementCount) + 5; - activateEle.style.setProperty('--height', `${navChildHeight}px`); - } - activateEle = activateEle.parentElement; - } - - // Scrolling to center active TOC element if TOC content is taller then viewport. - const tocElement = document.querySelector(CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini' ? '.sidebar-panel-container' : '.sidebar'); - if (!document.querySelector('.sidebar-toc-active')) return; - window.anime({ - targets : tocElement, - duration : 200, - easing : 'linear', - scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top - }); - }, - - updateSidebarPosition: function() { - if (window.innerWidth < 1200 || CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') return; - // Expand sidebar on post detail page by default, when post has a toc. - const hasTOC = document.querySelector('.post-toc:not(.placeholder-toc)'); - let display = CONFIG.page.sidebar; - if (typeof display !== 'boolean') { - // There's no definition sidebar in the page front-matter. - display = CONFIG.sidebar.display === 'always' || (CONFIG.sidebar.display === 'post' && hasTOC); - } - if (display) { - window.dispatchEvent(new Event('sidebar:show')); - } - }, - - activateSidebarPanel: function(index) { - const sidebar = document.querySelector('.sidebar-inner'); - const activeClassNames = ['sidebar-toc-active', 'sidebar-overview-active']; - if (sidebar.classList.contains(activeClassNames[index])) return; - - const panelContainer = sidebar.querySelector('.sidebar-panel-container'); - const tocPanel = panelContainer.firstElementChild; - const overviewPanel = panelContainer.lastElementChild; - - let postTOCHeight = tocPanel.scrollHeight; - // For TOC activation, try to use the animated TOC height - if (index === 0) { - const nav = tocPanel.querySelector('.nav'); - if (nav) { - postTOCHeight = parseInt(nav.style.getPropertyValue('--height'), 10); - } - } - const panelHeights = [ - postTOCHeight, - overviewPanel.scrollHeight - ]; - panelContainer.style.setProperty('--inactive-panel-height', `${panelHeights[1 - index]}px`); - panelContainer.style.setProperty('--active-panel-height', `${panelHeights[index]}px`); - - sidebar.classList.replace(activeClassNames[1 - index], activeClassNames[index]); - }, - - updateFooterPosition: function() { - if (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') return; - function updateFooterPosition() { - const footer = document.querySelector('.footer'); - const containerHeight = document.querySelector('.main').offsetHeight + footer.offsetHeight; - footer.classList.toggle('footer-fixed', containerHeight <= window.innerHeight); - } - - updateFooterPosition(); - window.addEventListener('resize', updateFooterPosition); - window.addEventListener('scroll', updateFooterPosition, { passive: true }); - }, - - getScript: function(src, options = {}, legacyCondition) { - if (typeof options === 'function') { - return this.getScript(src, { - condition: legacyCondition - }).then(options); - } - const { - condition = false, - attributes: { - id = '', - async = false, - defer = false, - crossOrigin = '', - dataset = {}, - ...otherAttributes - } = {}, - parentNode = null - } = options; - return new Promise((resolve, reject) => { - if (condition) { - resolve(); - } else { - const script = document.createElement('script'); - - if (id) script.id = id; - if (crossOrigin) script.crossOrigin = crossOrigin; - script.async = async; - script.defer = defer; - Object.assign(script.dataset, dataset); - Object.entries(otherAttributes).forEach(([name, value]) => { - script.setAttribute(name, String(value)); - }); - - script.onload = resolve; - script.onerror = reject; - - if (typeof src === 'object') { - const { url, integrity } = src; - script.src = url; - if (integrity) { - script.integrity = integrity; - script.crossOrigin = 'anonymous'; - } - } else { - script.src = src; - } - (parentNode || document.head).appendChild(script); - } - }); - }, - - loadComments: function(selector, legacyCallback) { - if (legacyCallback) { - return this.loadComments(selector).then(legacyCallback); - } - return new Promise(resolve => { - const element = document.querySelector(selector); - if (!CONFIG.comments.lazyload || !element) { - resolve(); - return; - } - const intersectionObserver = new IntersectionObserver((entries, observer) => { - const entry = entries[0]; - if (!entry.isIntersecting) return; - - resolve(); - observer.disconnect(); - }); - intersectionObserver.observe(element); - }); - } -}; diff --git a/leancloud_counter_security_urls.json b/leancloud_counter_security_urls.json deleted file mode 100644 index 479331d44..000000000 --- a/leancloud_counter_security_urls.json +++ /dev/null @@ -1 +0,0 @@ -[{"title":"Android 音频基础知识","url":"/2018/10/26/android-audio-base/"},{"title":"Android 音频录制与播放","url":"/2018/10/27/android-audio/"},{"title":"Android 定位知多少(一)","url":"/2019/05/26/android-location1/"},{"title":"Android 定位知多少(二)","url":"/2019/05/26/android-location2/"},{"title":"Android XML字符串","url":"/2019/10/27/android-string/"},{"title":"BigDecimal","url":"/2019/10/20/bigdecimal/"},{"title":"Charles 使用教程","url":"/2018/11/29/charles/"},{"title":"Google Cloud Platform for VPN","url":"/2018/11/07/cloud-gcp/"},{"title":"该死的 Base64,我惹你了?","url":"/2020/11/27/damn-base64/"},{"title":"系统间数据同步","url":"/2020/07/21/data-sync/"},{"title":"发布 jar 到 Maven中央仓库","url":"/2019/07/21/deploy-maven/"},{"title":"IDEA 之 SpringBoot 应用部署","url":"/2019/11/19/deploy-springboot/"},{"title":"Docker 之 SpringBoot 项目部署","url":"/2019/01/10/docker-init/"},{"title":"Fiddler 初体验","url":"/2018/10/25/fiddler/"},{"title":"Flowable(一)初识","url":"/2019/09/25/flowable1/"},{"title":"Flowable(二)Modeler-UI 集成","url":"/2019/10/24/flowable2/"},{"title":"Flowable(三)流程图绘制异常问题","url":"/2020/07/01/flowable3/"},{"title":"Flowable(四)流程相关知识点","url":"/2020/07/21/flowable4/"},{"title":"Flutter(一)之环境搭建","url":"/2018/12/16/flutter-init/"},{"title":"评论不自由,赞美无意义","url":"/2021/05/05/freedom-pact/"},{"title":"专治各种网络不服","url":"/2020/02/27/fuck-gfw/"},{"title":"Google Developer Days 2019","url":"/2019/09/12/gdd-2019/"},{"title":"Git 多账号","url":"/2018/10/06/git-account/"},{"title":"Git 常用命令","url":"/2018/10/07/git-bash/"},{"title":"Git 签名","url":"/2024/06/17/git-signature/"},{"title":"Git 子仓库管理","url":"/2018/05/17/git-sub/"},{"title":".gitignore 基础知识","url":"/2018/04/13/gitignore/"},{"title":"Gitlab 应用搭建","url":"/2018/04/24/gitlab1/"},{"title":"Gradle(一)基础","url":"/2020/12/10/gradle1/"},{"title":"Gradle(二)Android","url":"/2020/12/15/gradle2/"},{"title":"Gradle(三)SpringBoot 单工程","url":"/2020/12/16/gradle3/"},{"title":"Hexo Blog 高级指南","url":"/2020/11/20/hexo-advanced/"},{"title":"Hexo Blog 搭建","url":"/2018/03/25/hexo-blog/"},{"title":"Hexo Blog 迭代","url":"/2018/05/02/hexo-iterative/"},{"title":"Hugo 初体验","url":"/2018/07/11/hugo/"},{"title":"IDEA 多模块项目","url":"/2019/01/10/idea-multi-module/"},{"title":"开发小技巧","url":"/2019/01/02/idea-skill/"},{"title":"JetBrains 系列激活","url":"/2020/02/27/idea-welfare/"},{"title":"2020 年秋季面试经历","url":"/2020/11/15/interview-2020/"},{"title":"Linux 常用应用安装","url":"/2018/05/15/linux-build/"},{"title":"Linux 之 MySQL","url":"/2018/07/23/linux-mysql/"},{"title":"Lombok","url":"/2019/08/21/lombok/"},{"title":"应该知道的系统环境配置文件","url":"/2018/11/24/mac-bash/"},{"title":"Aria2 之 macOS","url":"/2018/12/12/mac-download/"},{"title":"MacBook Pro 初始化","url":"/2018/11/10/mac-init/"},{"title":"iTerm2 日常","url":"/2020/08/05/mac-item2/"},{"title":"MacBook Pro 疑难杂症","url":"/2020/11/13/mac-question/"},{"title":"品·杭州","url":"/2018/04/29/memory-hz1/"},{"title":"微服务架构 - Alibaba 生态整合(一)","url":"/2020/11/11/microservices-alibaba1/"},{"title":"微服务架构 - SpringCloud 生态整合(一)","url":"/2020/11/08/microservices-springcloud1/"},{"title":"【译】• 微服务","url":"/2019/06/01/microservices/"},{"title":"第 100 篇原创文章","url":"/2020/12/31/milepost1/"},{"title":"《激战》","url":"/2018/10/03/movie-fierce/"},{"title":"MQ 系列 — RabbitMQ(一)环境搭建","url":"/2020/11/10/mq-rabbit1/"},{"title":"MQ 系列 — RocketMQ(一)环境搭建","url":"/2020/11/10/mq-rocket1/"},{"title":"OSS 之 Minio 初体验","url":"/2021/03/16/minio/"},{"title":"MySQL 必备技能","url":"/2019/11/01/mysql1/"},{"title":"Netty(四)之 gRPC","url":"/2020/04/12/netty-grpc/"},{"title":"Netty(二)之 Protobuf","url":"/2019/11/29/netty-protobuf/"},{"title":"Netty(三)之 Thrift","url":"/2019/12/01/netty-thrift/"},{"title":"Netty初体验(一)","url":"/2019/11/20/netty/"},{"title":"Http VS Https","url":"/2018/06/22/network-http/"},{"title":"Network(一) 之OkHttp 入门","url":"/2018/06/23/network-okhttp1/"},{"title":"OOAD 与 UML","url":"/2019/05/27/ooad-uml/"},{"title":"开源协议,该如何选择","url":"/2020/11/25/open-license/"},{"title":"OSS 初体验","url":"/2021/03/27/oss/"},{"title":"迷宫如意琳琅图籍","url":"/2020/12/10/play-maze/"},{"title":"搞定 m.2 接口 SSD","url":"/2020/12/10/play-ssd/"},{"title":"Api 文档管理系统 RAP1环境搭建","url":"/2018/03/27/rap1/"},{"title":"Api 文档管理系统 RAP2环境搭建","url":"/2018/03/27/rap2/"},{"title":"Realm 数据库快速上手","url":"/2018/04/24/realm/"},{"title":"RxJava 入门","url":"/2018/10/02/rxjava/"},{"title":"琅嬛福地","url":"/2021/03/06/scenically/"},{"title":"非对称加密——RSA","url":"/2018/07/03/security-rsa/"},{"title":"【译】• 面向服务的架构","url":"/2019/06/19/soa/"},{"title":"SpringBoot(一) 初识","url":"/2019/06/23/springboot1/"},{"title":"SpringBoot(十)Mybatis 常用标签","url":"/2020/03/24/springboot10/"},{"title":"SpringBoot 源码构建","url":"/2020/12/31/springboot11/"},{"title":"SpringBoot(二) 启动分析JarLauncher","url":"/2019/07/05/springboot2/"},{"title":"SpringBoot(三) JDWP远程调用","url":"/2019/07/11/springboot3/"},{"title":"SpringBoot(四)配置文件","url":"/2019/07/28/springboot4/"},{"title":"SpringBoot(五)多环境配置","url":"/2020/02/02/springboot5/"},{"title":"SpringBoot(六)日志管理","url":"/2020/02/22/springboot6/"},{"title":"SpringBoot(七)注解","url":"/2020/02/25/springboot7/"},{"title":"SpringBoot(八)SpringApplication 源码分析","url":"/2020/02/26/springboot8/"},{"title":"SpringBoot(九)普通类如何获取配置文件中的值","url":"/2020/06/26/springboot9/"},{"title":"SpringCloud(一)Security OAuth2","url":"/2020/07/11/springcloud1/"},{"title":"SpringCloud(二)Security OAuth2 的 四种授权模式","url":"/2020/07/12/springcloud2/"},{"title":"SpringCloud(三)Security OAuth2 源码分析","url":"/2020/07/25/springcloud3/"},{"title":"构建基础SSM框架","url":"/2018/05/20/ssm/"},{"title":"复盘 2019 —— 安全上车","url":"/2020/01/22/summary-2019/"},{"title":"Git 同步 Fork 项目","url":"/2018/08/01/syncing-a-fork/"},{"title":"时间(一)之基础概念","url":"/2020/04/07/time1/"},{"title":"时间(二)之核心类","url":"/2020/04/08/time2/"},{"title":"时间(三)之格式化解析和时间计算","url":"/2020/04/09/time3/"},{"title":"时间(四)之主流框架中使用","url":"/2020/04/10/time4/"},{"title":"重要说明","url":"/2022/06/05/top1/"},{"title":"长沙","url":"/2020/05/01/travel-cs/"},{"title":"川藏行 —— 人在囧途","url":"/2020/10/13/travel-cz-feel/"},{"title":"川藏行 —— 行程规划","url":"/2020/09/13/travel-cz-plan/"},{"title":"忆·黄山","url":"/2018/05/01/travel-hs/"},{"title":"行·张家界","url":"/2018/05/20/travel-zjj/"},{"title":"藏经阁","url":"/2018/07/16/treasure/"},{"title":"微信小程序之 Vant实战(一)","url":"/2021/02/12/wechat-mini1/"},{"title":"Windows 之 常用应用安装","url":"/2019/09/25/windows-devtool/"},{"title":"Zxing(一)二维码基础知识","url":"/2019/05/01/zxing1/"},{"title":"Zxing(二)Android 模块应用源码探索","url":"/2019/05/02/zxing2/"}] \ No newline at end of file diff --git a/lib/hbe.js b/lib/hbe.js deleted file mode 100644 index 71205dd75..000000000 --- a/lib/hbe.js +++ /dev/null @@ -1,297 +0,0 @@ -(() => { - 'use strict'; - - const cryptoObj = window.crypto || window.msCrypto; - const storage = window.localStorage; - - const storageName = 'hexo-blog-encrypt:#' + window.location.pathname; - const keySalt = textToArray('hexo-blog-encrypt的作者们都是大帅比!'); - const ivSalt = textToArray('hexo-blog-encrypt是地表最强Hexo加密插件!'); - -// As we can't detect the wrong password with AES-CBC, -// so adding an empty div and check it when decrption. -const knownPrefix = ""; - - const mainElement = document.getElementById('hexo-blog-encrypt'); - const wrongPassMessage = mainElement.dataset['wpm']; - const wrongHashMessage = mainElement.dataset['whm']; - const dataElement = mainElement.getElementsByTagName('script')['hbeData']; - const encryptedData = dataElement.innerText; - const HmacDigist = dataElement.dataset['hmacdigest']; - - function hexToArray(s) { - return new Uint8Array(s.match(/[\da-f]{2}/gi).map((h => { - return parseInt(h, 16); - }))); - } - - function textToArray(s) { - var i = s.length; - var n = 0; - var ba = new Array() - - for (var j = 0; j < i;) { - var c = s.codePointAt(j); - if (c < 128) { - ba[n++] = c; - j++; - } else if ((c > 127) && (c < 2048)) { - ba[n++] = (c >> 6) | 192; - ba[n++] = (c & 63) | 128; - j++; - } else if ((c > 2047) && (c < 65536)) { - ba[n++] = (c >> 12) | 224; - ba[n++] = ((c >> 6) & 63) | 128; - ba[n++] = (c & 63) | 128; - j++; - } else { - ba[n++] = (c >> 18) | 240; - ba[n++] = ((c >> 12) & 63) | 128; - ba[n++] = ((c >> 6) & 63) | 128; - ba[n++] = (c & 63) | 128; - j += 2; - } - } - return new Uint8Array(ba); - } - - function arrayBufferToHex(arrayBuffer) { - if (typeof arrayBuffer !== 'object' || arrayBuffer === null || typeof arrayBuffer.byteLength !== 'number') { - throw new TypeError('Expected input to be an ArrayBuffer') - } - - var view = new Uint8Array(arrayBuffer) - var result = '' - var value - - for (var i = 0; i < view.length; i++) { - value = view[i].toString(16) - result += (value.length === 1 ? '0' + value : value) - } - - return result - } - - async function getExecutableScript(oldElem) { - let out = document.createElement('script'); - const attList = ['type', 'text', 'src', 'crossorigin', 'defer', 'referrerpolicy']; - attList.forEach((att) => { - if (oldElem[att]) - out[att] = oldElem[att]; - }) - - return out; - } - - async function convertHTMLToElement(content) { - let out = document.createElement('div'); - out.innerHTML = content; - out.querySelectorAll('script').forEach(async (elem) => { - elem.replaceWith(await getExecutableScript(elem)); - }); - - return out; - } - - function getKeyMaterial(password) { - let encoder = new TextEncoder(); - return cryptoObj.subtle.importKey( - 'raw', - encoder.encode(password), - { - 'name': 'PBKDF2', - }, - false, - [ - 'deriveKey', - 'deriveBits', - ] - ); - } - - function getHmacKey(keyMaterial) { - return cryptoObj.subtle.deriveKey({ - 'name': 'PBKDF2', - 'hash': 'SHA-256', - 'salt': keySalt.buffer, - 'iterations': 1024 - }, keyMaterial, { - 'name': 'HMAC', - 'hash': 'SHA-256', - 'length': 256, - }, true, [ - 'verify', - ]); - } - - function getDecryptKey(keyMaterial) { - return cryptoObj.subtle.deriveKey({ - 'name': 'PBKDF2', - 'hash': 'SHA-256', - 'salt': keySalt.buffer, - 'iterations': 1024, - }, keyMaterial, { - 'name': 'AES-CBC', - 'length': 256, - }, true, [ - 'decrypt', - ]); - } - - function getIv(keyMaterial) { - return cryptoObj.subtle.deriveBits({ - 'name': 'PBKDF2', - 'hash': 'SHA-256', - 'salt': ivSalt.buffer, - 'iterations': 512, - }, keyMaterial, 16 * 8); - } - - async function verifyContent(key, content) { - const encoder = new TextEncoder(); - const encoded = encoder.encode(content); - - let signature = hexToArray(HmacDigist); - - const result = await cryptoObj.subtle.verify({ - 'name': 'HMAC', - 'hash': 'SHA-256', - }, key, signature, encoded); - console.log(`Verification result: ${result}`); - if (!result) { - alert(wrongHashMessage); - console.log(`${wrongHashMessage}, got `, signature, ` but proved wrong.`); - } - return result; - } - - async function decrypt(decryptKey, iv, hmacKey) { - let typedArray = hexToArray(encryptedData); - - const result = await cryptoObj.subtle.decrypt({ - 'name': 'AES-CBC', - 'iv': iv, - }, decryptKey, typedArray.buffer).then(async (result) => { - const decoder = new TextDecoder(); - const decoded = decoder.decode(result); - - // check the prefix, if not then we can sure here is wrong password. - if (!decoded.startsWith(knownPrefix)) { - throw "Decode successfully but not start with KnownPrefix."; - } - - const hideButton = document.createElement('button'); - hideButton.textContent = 'Encrypt again'; - hideButton.type = 'button'; - hideButton.classList.add("hbe-button"); - hideButton.addEventListener('click', () => { - window.localStorage.removeItem(storageName); - window.location.reload(); - }); - - document.getElementById('hexo-blog-encrypt').style.display = 'inline'; - document.getElementById('hexo-blog-encrypt').innerHTML = ''; - document.getElementById('hexo-blog-encrypt').appendChild(await convertHTMLToElement(decoded)); - document.getElementById('hexo-blog-encrypt').appendChild(hideButton); - - // support html5 lazyload functionality. - document.querySelectorAll('img').forEach((elem) => { - if (elem.getAttribute("data-src") && !elem.src) { - elem.src = elem.getAttribute('data-src'); - } - }); - - // support theme-next refresh - window.NexT && NexT.boot && typeof NexT.boot.refresh === 'function' && NexT.boot.refresh(); - - // TOC part - var tocDiv = document.getElementById("toc-div"); - if (tocDiv) { - tocDiv.style.display = 'inline'; - } - - var tocDivs = document.getElementsByClassName('toc-div-class'); - if (tocDivs && tocDivs.length > 0) { - for (var idx = 0; idx < tocDivs.length; idx++) { - tocDivs[idx].style.display = 'inline'; - } - } - - // trigger event - var event = new Event('hexo-blog-decrypt'); - window.dispatchEvent(event); - - return await verifyContent(hmacKey, decoded); - }).catch((e) => { - alert(wrongPassMessage); - console.log(e); - return false; - }); - - return result; - - } - - function hbeLoader() { - - const oldStorageData = JSON.parse(storage.getItem(storageName)); - - if (oldStorageData) { - console.log(`Password got from localStorage(${storageName}): `, oldStorageData); - - const sIv = hexToArray(oldStorageData.iv).buffer; - const sDk = oldStorageData.dk; - const sHmk = oldStorageData.hmk; - - cryptoObj.subtle.importKey('jwk', sDk, { - 'name': 'AES-CBC', - 'length': 256, - }, true, [ - 'decrypt', - ]).then((dkCK) => { - cryptoObj.subtle.importKey('jwk', sHmk, { - 'name': 'HMAC', - 'hash': 'SHA-256', - 'length': 256, - }, true, [ - 'verify', - ]).then((hmkCK) => { - decrypt(dkCK, sIv, hmkCK).then((result) => { - if (!result) { - storage.removeItem(storageName); - } - }); - }); - }); - } - - mainElement.addEventListener('keydown', async (event) => { - if (event.isComposing || event.keyCode === 13) { - const password = document.getElementById('hbePass').value; - const keyMaterial = await getKeyMaterial(password); - const hmacKey = await getHmacKey(keyMaterial); - const decryptKey = await getDecryptKey(keyMaterial); - const iv = await getIv(keyMaterial); - - decrypt(decryptKey, iv, hmacKey).then((result) => { - console.log(`Decrypt result: ${result}`); - if (result) { - cryptoObj.subtle.exportKey('jwk', decryptKey).then((dk) => { - cryptoObj.subtle.exportKey('jwk', hmacKey).then((hmk) => { - const newStorageData = { - 'dk': dk, - 'iv': arrayBufferToHex(iv), - 'hmk': hmk, - }; - storage.setItem(storageName, JSON.stringify(newStorageData)); - }); - }); - } - }); - } - }); - } - - hbeLoader(); - -})(); diff --git a/links/index.html b/links/index.html deleted file mode 100644 index 1f262bbfd..000000000 --- a/links/index.html +++ /dev/null @@ -1,358 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -友链 | 星海 - - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
- -

友链 -

- - - -
- - - - -
-

排名不分先后顺序

-
-

博客

- -

网站

- - -
- - - -
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/package.json b/package.json new file mode 100644 index 000000000..3fb75014d --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "incoder", + "version": "2.0.0", + "private": true, + "scripts": { + "build": "hexo generate", + "clean": "hexo clean", + "deploy": "hexo deploy", + "server": "hexo server" + }, + "hexo": { + "version": "7.2.0" + }, + "dependencies": { + "hexo": "^7.2.0", + "hexo-blog-encrypt": "^3.1.9", + "hexo-deployer-git": "^4.0.0", + "hexo-generator-archive": "^2.0.0", + "hexo-generator-category": "^2.0.0", + "hexo-generator-feed": "^3.0.0", + "hexo-generator-index": "^3.0.0", + "hexo-generator-searchdb": "^1.4.1", + "hexo-generator-tag": "^2.0.0", + "hexo-hide-posts": "^0.4.3", + "hexo-leancloud-counter-security": "^1.5.0", + "hexo-renderer-ejs": "^2.0.0", + "hexo-renderer-markdown-it": "^7.1.1", + "hexo-renderer-stylus": "^3.0.1", + "hexo-server": "^3.0.0", + "hexo-symbols-count-time": "^0.7.1", + "hexo-theme-next": "^8.20.0" + } +} diff --git a/page/10/index.html b/page/10/index.html deleted file mode 100644 index e3307e644..000000000 --- a/page/10/index.html +++ /dev/null @@ -1,1416 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

个人博客使用 Hexo 搭建,使用效果很不错,RootCluster 组织主要存放自己新技术的学习和一些Demo实验。该组织同样也可以使用Github pages服务,因此也需要给RootCluster构建一个静态页面,可用直观清晰的看自己的项目,虽然之前已使用Hexo构建,为了了解其他的静态页面构建,所以这次选择了 Hugo

-

Hugo 是世界上最快的静态网站引擎。它是用 Go(aka Golang)编写的,由 bepspf13朋友开发

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

这是常用加密技术的系列文章,主要包含非对称对称JWT三类常用技术的应用

-

RSA

-

RSA:RSA加密算法是一种 非对称 加密算法。在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。RSA就是他们三人姓氏开头字母拼在一起组成的。

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

自从Android4.4的源码中可以看到HttpURLConnection已经替换成OkHttp开始( JakeWharton曾在Twitter表示 ) ,OkHttp+Retrofit+RxJava的组合网络请求一直经久不衰,主流app的网络架构基本都是这样的组合模式,存在即合理,说明OkHttp+Retrofit+RxJava的方式确实给开发,用户体验等带来可观的优势,那么这个系列文章围绕Android的网络展开.

-

OkHttp:An HTTP & HTTP/2 client for Android and Java applications

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

基础名称

-

请求报文

-

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:
-请求行(request line)、请求头(header)、请求内容组成,如下请求报文的一般格式。
-请求报文

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-
    -
  • 时间:2018.06.16——2018.06.19
  • -
  • 地点:杭州——张家界
  • -
  • 目标:武陵源景区,天门山景区,大峡谷景区
  • -
-
-

听说张家界是人间仙境,鬼斧神工,嗯,今年端午就去一探究竟,慌慌张张,匆匆忙忙做一份旅行攻略,翻遍百度,爬烂谷歌,都没有找到匹配的攻略,哎,可能是我姿势不对?!

-

张家界,张家界景区共分为四块:张家界国家森林公园杨家界自然保护区天子山自然保护区索溪峪自然保护区四大景区,统称为武陵源风景名胜。

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

SSM结构

-

SSM

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在使用 NexT 作为 Hexo 博客的主题时,不能 友好 的支持其主题的更新,以及 多设备 之间的主题同步。

-

按照官方提供的导入主题操作指引

-
1
2
$ cd hexo
$ git clone https://github.com/theme-next/hexo-theme-next themes/next
-

发现commit并push到GitHub的远程服务器上,发现 themes/next 路径下并不能打开和查看该路径下的文件,原因是NexT是当前项目的一个子仓库(项目),在 Github 上对于之仓库项目的引用,推荐使用 git subtree 命令来进行对子仓库的管理,不推荐直接拷贝需要子仓库的代码到自己的项目中

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

作为 Android 开发者,目标主要是在客户端,平时也就是和服务端对接数据接口,很少直接干到服务端的 Linux 机器,随着这波推动团队技术平台基础开发工具模块的完善,拿到了一台 Linux 机器,重新构建移动端的测试服务器。

-

该机器主要功能:

-
    -
  1. 提供移动端服务 Api 接口
  2. -
  3. 提供移动端通讯录管理授权服务
  4. -
  5. 提供企业微信通讯录同步服务
  6. -
  7. 管理移动端服务器 Api 接口文档
  8. -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

最初博客通过Cloudflare反向代理进行HTTPS解析,放完五一假期,Github官方开始支持自定义域名的HTTPS解析,在使用Cloudflare期间,经常性的521等问题烦恼,这次也可以名正言顺的弃用CloudFlare

-

本次迭代内容

-
    -
  • 弃用Cloudflare
  • -
  • 自动化部署
  • -
  • 常用设置
  • -
  • 常用插件安装
  • -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-
-

黄山归来不看岳

- -
-

五岳未归,先品黄山。以前看黄山还是小学课本《黄山》一文介绍黄山的美,黄山的秀丽,黄山的与众不同,这次是亲身去体验黄山的姿态;趁着五一,趁着年轻,趁着…。废话不多讲,先看黄山日出美景

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/page/11/index.html b/page/11/index.html deleted file mode 100644 index 3f1aaf385..000000000 --- a/page/11/index.html +++ /dev/null @@ -1,1075 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

上有天堂,下游苏杭,杭州,一个温文尔雅,一个记忆中天堂,一个南方姑娘的城市。
-杭州:毕业后的第二个城市,很开心在这样的城市生活,工作,结识这里的人,杭州和家乡的气候非常相似,因此在杭州有种在家的感觉,在这里遇到的的人,我都会记着你们美丽帅气的脸庞

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

我司团队之前一直使用SVN来进行代码托管,主要问题

-
    -
  1. 每次来个新人都需要找对应的SVN管理员进行授权分配指定的仓库操作权限,有时候需要多个项目切换,还得再次提出进行仓库的指定
  2. -
  3. SVN都是以中文命名,这其实没啥,但是在eclipse 以及IDEAXcode等开发工具,链接地址都会把中文字进行编码,造成路径非常的长,强迫症的我这怎么忍得了
  4. -
  5. 产品相关的,设计相关的啥也都放在SVN里面,搞得SVN里面鱼龙混杂
  6. -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

realm-db

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

.gitignore顾名思义是Git中用来管理所需要忽略或者说不用纳入版本控制文件

-

基本配置语法

-
    -
  1. “#“:表示注释
  2. -
  3. “/“:表示目录
  4. -
  5. “*“:表示通配符,用来通配多个字符
  6. -
  7. “?“:表示通配单个字符
  8. -
  9. “[]“:表示包含单个字符的匹配列表
  10. -
  11. “!“:表示不忽略匹配到的文件或者目录
  12. -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

RAP2是采用前后端分离的形式,因此搭建完整的RAP2需要 服务端:rap2-delos客户端:rap2-dolores 同时部署

-

部署RAP2需要亲具有Node+Linux+MySQL的运维知识,如果亲对此不是很了解,建议用http://rap2.taobao.org 线上版本就可以

-

由于 客户端:rap2-dolores 是建立在 服务端:rap2-delos 基础上,因此先搭建服务端应用

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

前后端分离的路上,一款强大的API管理工具,可以降低沟通成本,大大提高开发效率,节省的时间,让我们去做更有意义的事情。

-

API管理工具有很多,选择适合自身需求的就是最好

-

这里以阿里妈妈出品的RAP产品;目前RAP分为: RAP1RAP2

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

之前一直纠结用Jekyll还是Hexo来搭建GitHub Page博客,原本一直想搭建一个Material Design主题风格,从Hexo Themes中寻找到一款不错的主题,indigo是一款支持IE10+,评论,目录导航,分享等功能的轻量Blog主题。

-

简单的修改了该主题之后,本地预览都没有什么问题,但是部署到Github上,样式什么的都无法加载,应该是我的操作姿势不对吧,调整了半天没有解决,烦躁中找到之前star的另一款很受欢迎的Next主题。

-

既然自己修改的无法正常部署预览,那就用别人写好的吧,刚好赶上Next新版本V6.0系列的推出,那就不废话,直接开干

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/page/2/index.html b/page/2/index.html deleted file mode 100644 index 65fb90adc..000000000 --- a/page/2/index.html +++ /dev/null @@ -1,1375 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在上一篇 Gradle 的文章中,已经对 Gradle 有了一定的认识,Gradle 在 Android 有着广泛的应用,用作 Android 包依赖管理,应用构建,测试,等一些列自动化,我们本篇就来了解下在 Android 领域 Gradle 的使用。其实 Android 项目结构和之前在第一篇 Gradle 项目结构基本相同,只是在 module 级别多了的 proguard-rules.pro。对于不管是 Android 项目或是 Spring 系列项目的子 module 都会有 build.gradle 文件

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

GitHub 上 Gralde 是这样描述,“Adaptable, fast automation for all”(让一切都能快速自动化
-Gradle是一个构建工具,专注于构建自动化和对多语言开发的支持。对于在任何平台上的构建,测试,发布和部署,Gralde 提供了一种灵活的模型,可以支持从编译和打包代码到发布网站的整个生命周期。Gralde 旨在支持跨多种语言和平台的构建自动化,包括 Java,Scala,Android,Kotlin,C/C++ 和 Groovy,并于开发工具和包括 Eclipse,IntelliJ 和 Jenkins 的持续集成服务器紧密集成

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在上一个项目中,由于客观原因,双方进行数据交换,用到对媒体文件(图片)进行了 Base64 编码处理,将编码后的数据存入了数据库,使用方再从数据库中取出数据进行解码恢复成图片,在实际处理中,这是最不推荐的做法。正确有效的做法是将资源文件存入到 OSS 系统中,数据库中记录文件的地址即可。但由于项目历史原因,无法使用 OSS 来处理,虽然说技术本质不难,编码存入,解码查看而已。但由于对方没有告知具体的编码方式,询问了好几次才最终给到对应的编码方式,浪费了大量的时间去沟通和试错,得不偿失

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

现如今软件行业的发展完全离不开开源社区,很多优秀的软件应用、技术都能看到开源软件的影子,我们都是站在巨人的肩膀上。对于软件行业的从业者,能为开源项目贡献自己的力量,或是将自己对某一个细分领域所做的研究实践开源出来,这是一件非常值得骄傲的事情。而要参与一个大型的开源项目,你除了需要该项目涉及的核心技术知识外,还需要了解一定的开源项目运转方式等,对于如何参与开源项目,这里暂不做过多的介绍,有兴趣的可以移步 Gitee 发起的《开源指北》项目,该项目中详细介绍了如何参与开源项目。本篇文章也不啰嗦这一点,仅仅围绕开源协议,我们应该清楚的常识和注意的点

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

NexTHexo 非常受欢迎的博客主题,方便简洁,但却不简单的功能,你可以在提供的强大功能基础上进行扩展或者自定义,来满足你的个性化需求。本篇文章主要是对应 NexT 提供的一些高级功能的使用,作为一个持续更新的文章吧,记录自己 SX 操作,当然也是我平时在使用 NexT 时遇到的一些问题的记录。好了废话不多说了,我们直接进入正题

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- 这是一篇加密博文,请输入密码后查看 - -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

这是一篇记录使用macOS系统时遇到的一些疑难杂症

-

macOS Big Sur

-

在 2020.11.13 正式推送了 macOS Big Sur version 11.0.1 版本,这一个版本是改动比较大的版本,这里关于它的新特性就不做介绍了,有兴趣的请查看官方网站介绍 Big Sur

-

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

曾几何时,市面上对于微服务,分两个派系,一个派系以阿里为主的 Dubbo 生态体系,还有一派以 Spring Cloud 生态为主的体系,这两个系列的讨论也一直没有停息过。但现在 Spring Cloud Alibaba 的出现,提供了一整套构建分布式应用开发的微服务组件,由于这些组件是构建在原生的 Spring Cloud 之上,因此其服务治理方面的能力可认为是 Spring Cloud Plus, 不仅完全覆盖 Spring Cloud 原生特性,而且提供更为稳定和成熟的实现。那么从本系列就开始跟着我一起用阿里系的应用搭建分布式微服务应用,满足企业级的应用需要,而不是停留在 Dome 级别的应用框架使用。废话不多说,我们一起开始这一系列的实践

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/page/3/index.html b/page/3/index.html deleted file mode 100644 index a4fada652..000000000 --- a/page/3/index.html +++ /dev/null @@ -1,1372 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

本篇我们来看 MQ 系列的另一个广泛使用的中间件 RocketMQ。官方介绍到 “Apache RocketMQ™ 是一个统一的消息传递引擎,轻量级的数据处理平台。Apache RocketMQ 是一个分布式消息传递和流媒体平台,具有低延迟,高性能和可靠性,万亿级容量和灵活的可伸缩性” 。更重要的是在分布式消息队列中,目前唯一提供完整的事务消息的,只有 RocketMQ。

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

微服务这一概念在 2014 年的 3 月份随着 James Lewis 和 Martin Fowler 在博客中对于微服务这一概念做出了详细的阐述,开始走进开发者的视野。在 Spring 官方的加持下,助推微服务架构风格的应用开始火边整个后端领域。在早起微服务化的演进中 Netflix 的一些列开源的组件,迅速占领微服务生态中的 C 位,提供了网关路由,负载均衡,服务注册发现,服务通信,服务熔断限流等核心组件

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

这次假期安排算是毕业后,除去 2020 年春节因为疫情,假期最长的一个,满脑子写着高兴,以至于我出门前一刻,还在处理工作上的事情,浑浑噩噩的追下午的动车和好友汇合,哪知道这只是囧途的开始,在地铁上我快要困死过去,还好没有坐过站,之前有看一眼车站,但是并没有仔细看具体是哪个站,在我的潜意识里一直认为是东站,当我冲到检票口时,我刷身份证发现进不了站,工作人员说我跑错站,我当时一脸茫然(心想跑错站也没关系,只要它经过东站就可以),赶紧查看我的购票记录,发现自己真是跑错了车站而且还不经过东站,距离发车时间就剩 1 小时了

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

唠叨了很久的《成都》,这次终于要去见一见你了,成都在我的印象中是一个万物皆可辣,但也少不了让你肉跳的麻,那里有让你每天不重样的美食,有看不完的美景,也有能让你安逸的歌谣《成都》。早在去年十一就被《盛世中华》视频种草,时隔一年,这个心愿要就要被实现,这次的十一目标不仅是四川,还有西藏的风景,这是双份的快乐。

-

曾经记一个人是一篇文章,后来记一个人是一首歌,后来记一个人是一门学科,再后来记一个人是一段代码或语言,到现在记一个人是一个城市,因为那是有你的城市,那里有你的故事,好了废话有点多,先来看看这次出行的规划

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

-

iTerm2是一款优秀强大的第三方终端,相信用 Mac 的开发者,一定听过或者用过 iTerm2 这款终端应用,如果你还没使用过,没关系那么本篇文章就带你了解学习 iTerm2 中的一些常用操作,来提高你的工作效率

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

流程部署

-

流程部署涉及到的表有 act_re_deploymentact_re_procdef,act_ge_bytearry,act_ge_property

-
    -
  • act_re_deployment(部署对象表):存放流程定义的显示名和部署时间,每部署一次增加一条记录
  • -
  • act_re_procdef(流程定义表):存放流程定义的属性信息,部署每个新的流程都会在这张表中添加一条记录,当流程定义的 key 相同时,使用的是版本升级
  • -
  • act_ge_bytearry(资源文件表):存放流程定义相关的部署信息,即流程定义文档的存放处。每部署一次会增加两条记录,一条是关于 bpmn 规则文件,一条是生成的流程图片(如果部署时只指定了 bpmn 一个文件,flowable 会在部署时解析 bpmn 文件内容自动生成流程图)。两个文件都是以二进制形式存储在数据库中
  • -
  • act_ge_property:主键生成策略表
  • -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

对于不同系统之间的数据同步,业界通常有四种方式来进行数据交互,本篇文章就来聊一聊这四种数据交互方式

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在上一篇文章中,我们了解了 Security OAuth2 相关的一些基础知识,和整个四种授权模式的交互过程,那么本篇是对四种模式的实践,废话不多说,我们直接开始,SpringCloud 相关的实践代码均托管在rc-cluster-springcloud项目的中,项目使用的一些依赖版本如下

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/page/4/index.html b/page/4/index.html deleted file mode 100644 index 9fdef2f6e..000000000 --- a/page/4/index.html +++ /dev/null @@ -1,1371 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

什么是 OAuth2

-

用于 REST/APIs 的代理授权框架(delegated authorization framework),基于令牌 Token 的授权,在无需暴露用户密码的情况下,使应用能获取对用户数据的有限访问权限,做到解耦认证和授权

-

OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如:头像,照片,视频等),而在这个过程中无线将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- -

附录

-
    -
  • HttpMediaTypeNotAcceptableException
  • -
  • HttpMediaTypeNotAcceptableException in Spring MVC
  • -
- - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在之前的 SpringBoot 学习中,我们知道,可以在项目的 application.yml 文件或 application.properties 文件中获取到一些我们项目中的一些静态值。但对于一些非 Spring 所管理的类(比如一些工具类)该如何获取到定义在配置文件中的值呢?而这些工具类中的配置值和对应项目运行的环境有关,我们如果固定写在代码内,每次打包时去更改,显然这种做法不够好,那么本篇文章就来实践一些非 Spring 类如何获取配置的值

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- -

总结

-

附录

-
    -
  • Java SE 8 日期和时间
  • -
  • JSR310新日期API(二)-日期时间API
  • -
  • Java中日期时间相关核心类
  • -
- - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- -

总结

-

附录

-
    -
  • Convert Date to LocalDate or LocalDateTime and Back
  • -
  • JSR310新日期API(三)-日期时间格式化与解析
  • -
  • Java中日期时间相关核心类
  • -
- - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

上一篇时间(一)文章,我们已经了解学习了时间相关的一些概念和时间相关的其他知识,那么本篇文章开始我们深入在计算机领域中关于时间的相关核心类的知识,先来让我们看看 JSR-310(JDK8+) 和之前(JDK7 之前)的比较

-

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-
-

时间是一种尺度,在物理定义是标量,借着时间,事件发生之先后可以按 过去-现在-未来 之序列得以确定(时间点),也可以衡量事件持续的期间以及事件之间和间隔长短(时间段) —— 维基百科

- -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - - -
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/page/5/index.html b/page/5/index.html deleted file mode 100644 index 190077a80..000000000 --- a/page/5/index.html +++ /dev/null @@ -1,1384 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

众所周知国内的开发已经由原来的“致敬”到现在软件生态领域的“引领”(目前来说还未到真正引领)世界技术发展,但是一个问题始终还是未能有所突破,作为中国的开发者每次在面对新的技术在环境搭建就劝退了一众人,很多开发所依赖的项目资源都来自国外服务,而由于中国的特殊,对很多国外服务的限制,让你在开始的第一阶段总是碰的鼻青脸肿,把大量的时间浪费在环境搭建的等待上,虽然现在很多开源组织或者一线大厂提供了相应的镜像服务方便国内的开发者,但很多都还需要我们自行去更改或者解决这些问题,本篇文章就是我的开发之路上的各种网络问题的解决办法

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

正如我们看到的 SpringBoot 应用启动入口类,main() 方法中一行简单 SpringApplication.run(MyApplication.class, args); 就可以将 SpringBoot 应用给启动了。那么它肯定是在SpringApplication中做了大量的工作,才能将应用启动,因此本篇文章我们来一起看看这个核心的类

-

SpringApplication 类可以从 Java 的 main 方法中引导和启动 Spring 的应用,默认情况下它会按照如下的启动步骤

-
    -
  1. 创建一个恰当的 ApplicationContext 实例(取决于你的 classpath 路径)
  2. -
  3. 注册一个 CommandLinePropertySource 将命令行参数作为 Spring 的属性(换句话说,可以通过命令行来传递当前应用所需要的一些属性)
  4. -
  5. 刷新应用的 Context(内容上下文),并加载所有单例的 beans
  6. -
  7. 触发每个 CommandLineRunner beans
  8. -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

SpringBoot 与 SpringCloud 微服务技术栈体系本质就是围绕注解来展开,这些注解在微服务框架中扮演非常重要的角色,每个注解都有他的应用场景,通过一些注解的组合让 SpringBoot 与 SpringCloud 开发变的简单和高效,本篇文章我们就来汇总 SpringBoot 相关的注解

-

本篇文章基于如下版本

-
    -
  • Spring:5.1.8 RELEASE
  • -
  • SpringBoot:2.1.6 RELEASE
  • -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

日志是我们项目开发过程中必不可少的一个方面, 当我们项目引入了 spring-boot-starter-web 这个 jar 包,会自动引入相关的一些日志相关的 jar 包,比如,其实在项目中可供我们选择的 jar 包有很多,比如 log4jlog4j2logback(现在使用最多),slfj等,在具体使用时并不会直接去使用log4jlog4j2而是使用slfj作为门面,具体的实现是通过可插拔的方式提供。logback实际是在log4j之后作者重新写的一个日志框架。本篇文章主要讲logback在项目中的应用

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在 SpringBoot 项目中,常用的包管理分别为 maven 和 gradle,在不同包管理下我们如何实现多环境的项目配置,这是实际项目开发过程汇总必备的一项技能,可以大大提高我们开发部署效率,同时也避免了人为的频繁改动配置造成的问题等,有些人可能会问了,maven 不是用的好好的嘛,干嘛还要用 gradle,首先我们可以看现在主流开源项目在提供引入方式时都是有提供了 gradle 依赖方式,以及 gradle 支持编写脚本,在很大程度上让管理更加便捷和人性化

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- 这是一篇加密博文,请输入密码后查看 - -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.

-

Apache Thrift软件框架,用于可扩展的跨语言服务开发,它包含软件栈和一个代码生成器用于构建服务,这个服务可以高效并且无缝的在 C++,Java,Python,PHP,Ruby,Erlang,Perl,Haskell,C#,Cocoa,Node.js,Smalltalk,OCaml 和 Delphi 等其他语言间协作

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

Netty 框架中已经默认支持了 Protobuf 格式的数据传输,因此我们本节就来学习 Protobuf,Protobuf 主要用于进行 RPC 数据传输(它是一种自定义协议,这种协议能更好,更小体积,对数据编解码【序列号和反序列化的过程】),在学习 Protobuf 之前我们先了解两个概念 RMI 和 RPC

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

Netty 是国内外各大互联网公司的必备网络应用框架,Netty 主要处理与网络相关的一些应用。由于 Netty 设计的巧妙的实现方式,以及对协议很好的实现,使的 Netty 可以在各种应用场景下广泛的应用,无论是传统基于HTTP协议的访问方式,还是更底层基于socket的访问方式,以及支持HTML5规范中的websocket的长连接特性,都提供了比较好的支持

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/page/6/index.html b/page/6/index.html deleted file mode 100644 index abbef4441..000000000 --- a/page/6/index.html +++ /dev/null @@ -1,1378 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

服务端由原来 混合式(Java+JSP)的方式演进成专注于提供服务 API(前后端分离)的方式,开发的明确分工,使的各自开发人员在各自领域的垂直技能的加强,以满足业务的快速迭代,因此也就在这两个方式中,项目的构建方式也有了一定的变化,混合模式中常编译为 war 包,而在前后端分离模式中常编译为 jar 包,这两种文件格式虽然都是一种压缩文件的格式,但实质还是有一些区别,那首先让我们来了解这两种文件它们之间的区别

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

Android在开发过程中,一些特殊字符时无法直接在 strings.xml 文件中写,需要用对应的转义字符代替或者在特殊符号(比如:´" 等待)前添加 \ ,比如一个 TextView 控件中,需要动态替换其中的一些数据,再比如需要调整 TextView 字体的一些HTML样式(比如:粗体,斜体,下划线等),虽然这些都可以用 TextView 去修改,但更简单的方法是设置string提供的属性即可

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

float 和 double 同样也是可以表示浮点数,为啥在对于要求精确的进度计算时,尤其是关于币值相关,都采用 BigDecimal 类型来处理?

-
    -
  1. float 和 double 类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的 快速近似 计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该用于要求精确结果的场合。——《Effective Java》
  2. -
  3. float 精度 7 位,double 精度 16 位
  4. -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

这是一篇在Windows系统下,持续更新常用开发软件安装汇总,当然一些简单得安装就在这里记录,不废话了

-

JDK

-

官方下载地址,选择需要的版本下载安装包

-

安装完成,设置环境变量,右击我的电脑–>属性–>高级系统设置–>高级–>环境变量

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据等,众所周知,Flowable是Activit的一个分叉,Flowable的第一个版本(5.22.0)是基于Activit(5.21.0),关于为什么Flowable会从Activit分叉,感兴趣可以查看Flowable官方的文章Flowable and Activiti: What the Fork?!,这里不在赘述这些内容

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

-

连续三年申请参加 Google Developer Days,今年终于中签了,而且和好友大蛇丸及公司同事同时中签(可能是我们都使用了忍术)。嗯,终于离404公司又进了一步,哈哈哈~
-废话不啰嗦了,这篇文章就唠唠参加 GDD 的前前后后。

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在实际开发过程中,不管是服务端(Java),还是客户端(Android)都需要创建对应的实例bean对象,用来实例化对象,在对需要实例化的对象中,通常需要写 setget 方法,字段少的时候,还能忍受,尤其当客户端字段多的时候,而且字段类型或者字段名称来回改动时,稍不注意,就很大机率修改不全面,就会造成一些隐藏bug,有没有不需要手动取写(快捷键生成)这些方法,当然有,本片文章,我们就来学习 Lombok

-

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
-Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more(Project Lombok是一个java库,可以自动插入编辑器并构建工具,为您的java增添色彩。 永远不要再写另一个getter或equals方法,使用一个注释,你的类有一个功能齐全的构建器,自动化你的日志记录变量等等),这是官方对Lombok的介绍,简单来讲就是通过注解的方式,代替一些重复性的代码,让代码更加简洁

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

关于 SpringBoot 配置文件,在之前的文章中已经提到配置文件格式,主要是两种格式的配置,这里并没有哪个配置写法一定优于另一种写法,对于配置文件名(application.yml 或者 application.properties),可以更改,为了减少不必要的麻烦,不建议修改,本篇文章以 yml 文件作为示例

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/page/7/index.html b/page/7/index.html deleted file mode 100644 index 814e53939..000000000 --- a/page/7/index.html +++ /dev/null @@ -1,1399 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

背景

-

和朋友一起维护的开源组织(我就是打个辅助,逃~),其中有一个系列的项目,这些项目统一通过 base 项目的 pom 文件管理这个系列项目依赖的第三方 jar,其他一些辅助项目(如:tools)项目主要是一些常用工具方法的封装,为了能让我们在不同机器,不同地点能够无缝切换,更重要的让使用的伙伴能以最简便的方式运行(避免不必要的配置),我们需要把通用的东西托管起来,那么就需要将这些配置依赖或辅助 jar 托管到 Maven中央仓库,话不多说,就跟着我的步骤来看看如何将 jar 发布到 Maven中央仓库

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在 SpringBoot 系列的第二篇文章中,已经详细分析了 SpringBoot 的启动过程,那么这篇文章,我们通过源码调试的方式来验证我们的分析,首先我们在控制台中输入 java 命令,可用输出 JDK 给我们提供了一些命令,其中-agentlib命令就是本篇文章所介绍,用于我们进行源码调试

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

我们在开发过程中,使用 java -jar you-jar-name.jar 命令来启动应用,它是如何启动?以及它如何去寻找 .class 文件并执行这些文件?本节就带着这两个问题,让我们一层层解开 SpringBoot 项目的 jar 启动过程,废话不多说,跟着我的脚步一起去探索 spring-boot-load 的秘密。

-

SpringBoot(一)初识 已经解释了为什么在编译后的 jar 中根目录存在 org/springframework/boot/loader 内容,以及为了方便学习研究,我们需要在项目的依赖中导入 org.springframework.boot:spring-boot-loader 依赖。同时我们在解压的 you-jar-name.jar 文件中,查看对应的清单文件 MANIFEST.MF 内容,其中明确指出了应用的入口 org.springframework.boot.loader.JarLauncher 因此我们就从 JarLauncher 开始一步步深入

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在学习过程中,我们首先需要将学习知识的基本概念搞清楚,而搞清楚概念最权威的方式是查阅 英文版 • 维基百科 ,或者是对应知识的官方文档上面查找相关的知识。这样学习才能学习到知识的精华,而不是阅读经过别人转译过的文章。因此,这篇文章仅是本人在学习 SOA 基本概念时,对维基百科知识的一个汇总翻译记录,不建议朋友把这篇文章当做你的学习资料,具体请查阅Service-oriented architecture

-

面向服务的架构(SOA)是一种软件设计风格。 SOA 服务通过应用组件,通过网络通信协议的方式向其他组件提供服务。SOA 的基本原则是独立于厂商,独立于产品以及独立于技术[1]。服务是一种功能的离散独立单元,可以远程访问并独立运行与更新,例如在线查询信用卡账单。

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

这是第一篇翻译文章,用于学习近些年火热的微服务,这篇是微服务概念是由 James Lewis 所著,虽然官网已有中文翻译,但是在学习过程中,应该应该动手输出,这样有助于对知识的理解和记忆,废话不多说,开始翻译

-

微服务

-
-

近些年术语“微服务架构”就像雨后春笋般蓬勃的发展,微服务描述软件应用设计是独立可部署服务一个特殊方式。虽然这些都不够准确的去定义一个架构风格,但存在一些通用的特质(大家达成共识的特征),如何去组织围绕业务能力,如何自动化部署,端点的智能发现,以及语言和数据去中心化的控制

-
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

OOAD(Object Oriented Analysis and Desigin) 是根据 OO 的方法学,对软件系统进行分析和设计的过程

-
    -
  • OOA(Object Oriented Analysis):分析阶段
  • -
  • OOD(Object Oriented Desigin):设计阶段
  • -
-

What to do

-

分析阶段主要解决以下问题

-
    -
  • 建立针对业务问题域的清晰视图
  • -
  • 列出系统必须要完成的核心任务
  • -
  • 针对问题域建立公共词汇表
  • -
  • 列出针对此问题域的最佳解决方案
  • -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

本篇主要讲解定位策略

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

手机行业持续不断发展,为我们生活带了很多便利,在我们生活中到处都存在它的痕迹,它不仅是一个工具而且还是有温度的组手,协助你解决生活中的各种问题,渐渐成为了人们不可或缺的“器官”。它为什么就能进化成人类的一部分呢?其中一个重要的功能就是定位,看似单一的功能却渗透了我们各种场景,比如:定位,导航,这种基础的功能,还基于定位社交聊天,运动轨迹画像,出行等等,解决了人与人,人与物,物与物之间在位置上的问题。那么我本节就来聊一聊定位相关的一些知识,以及手机是如何在 Android 系统中是如何进行定位的

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

ZXing(“Zebra Crossing”)用于Java,Android的条形码扫描库。虽然当前开源库仅处于维护模式,意味着更改是由贡献的补丁来驱动,只会考虑错误修复和次要的增强功能

-

本篇开启 ZXing项目Android 模块的探索学习之路,那么首先我们要集成该模块到项目中

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/page/8/index.html b/page/8/index.html deleted file mode 100644 index 69a8bf75e..000000000 --- a/page/8/index.html +++ /dev/null @@ -1,1385 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

移动端开发,一个避不开的老生常谈功能开发,二维码扫描识别(主要)及二维码生成(辅助),虽然已有现成的开源项目提供了功能,仅仅作为功能的开发集成和调试,其实远远不够,应该在完成功能开发的基础上去学习背后的技术点和原理,让我们更加完整的掌握该技术。废话不多说,本篇是 Zxing 相关技术的第一篇文章,本篇不会涉及到应用相关,仅仅是二维码基础知识的学习记录。

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
- - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

Jetbrains系列中IDEA是现如今公认最好用,最强大的Java开发工具,不接受任何反驳,本篇介绍macOS上使用 IDEA 创建 SpringBoot 多模块项目

-

准备工作

-
    -
  • 系统环境:macOS 10.14.2
  • -
  • 应用工具:IDEAMaven
  • -
-
-

这里不再介绍基本软件的安装及配置

-
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

Android Studio 是Google基于JetBrains的 IntelliJ IDEA 所定制开发的 Android 开发 IDE。因此这里的设置适用于 JetBrains 公司系列的开发工具,同样也适用于 Android Studio,这是一篇持续更新的文章,在平时的使用过程中一些习惯性的模板化的一些设置,可以减少我们一些重复性的操作,进而提高开发效率。

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

这两年随着前端的高速发展,大前端的趋势下,Native移动应用开发市场在一定程度上被前端瓜分,加之硬件的快速迭代,性能已不存在明显的短板,React NativeVueAngular等等这些Web框架,对移动端也有了较大的提升,毕竟这样的开发效率会直线上升,并且大大减少了成本。技术的革新真的好快,如果不去学习,很快就会被淘汰

-

那就直接进入正题,flutter是一站式跨平台解决方案,一次开发,适配整个移动平台,并且是由Google进行主导开发,开源的一个项目,现如今已经迭代到1.0版本

-

本篇文章主要记录在macOS系统上搭建flutter开发环境的过程

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

Aria2 是什么

-

Aria2 是一款支持多种协议的 轻量级命令行 下载工具。有以下特性:

-
    -
  • 多线程连线:Aria2 会自动从多个线程下载文件,并充分利用你的带宽;
  • -
  • 轻量:运行时不会占用过多资源,根据官方介绍,内存占用通常在 4MB~9MB,使用 BitTorrent 协议,下行速度 2.8MB/s 时 CPU 占用率约 6%;
  • -
  • 全功能 BitTorrent 客户端;
  • -
  • 支持 RPC 界面远程控制
  • -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

-

Charles is an HTTP proxy / HTTP monitor / Reverse Proxy that enables a developer to view all of the HTTP and SSL / HTTPS traffic between their machine and the Internet. This includes requests, responses and the HTTP headers (which contain the cookies and caching information)

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在计算机操作系统中Shell是用户与操作系统交互的媒介,而bash作为目前Linux\macOS系统中最常用的Shell,它支持的startup文件也并不单一,甚至让人感到费解,以下就是对Shell的学习

-

Shell:在计算机中,值“为用户提供用户界面”的软件,通常指的是 命令行界面 的解析器。一般来说,Shell指操作系统中提供访问内核所提供的服务程序。

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

今天拿到了一辆跑车 MBP,虽然不是顶配,也能算上中等吧,废话不啰嗦,上来就是一顿操作猛如虎,最终效果就是唬

-

跑车的一些零配件来源地Awesome MacMacWK 一些破解软件集合地

-

软件的安装,这里不再赘述,这里主要对常用开发软件的配置进行记录

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

cloud-gcp

-

随着云产品的普及推广,各路国际大场也是纷纷推出了相关云产品的试用,其中具有代表性的Google CloudAmazon,本篇主要讲解Googel Cloud产品的试用,并搭建SSR服务

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/page/9/index.html b/page/9/index.html deleted file mode 100644 index 367e32fdd..000000000 --- a/page/9/index.html +++ /dev/null @@ -1,1408 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -星海 - Life's A Struggle! - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

上一篇主要介绍了音频相关的一些基础知识,本篇主要介绍在Android系统中如何进行音频的录制,播放

-

音频录制

-

Android SDK中提供了AudioRecordMediaRecorder两个API经行音频的录制,具体的优缺点等如下:

-
    -
  • AudioRecord 『added in API level 3』(基于字节流录音):
    -优点:可以实现语音的实时处理,进行边录边播,对音频的实时处理。
    -缺点:输出的是PCM的语音数据,如果保存成音频文件是不能被播放器播放的。要用到这个去进行处理。
    -适用场景:需要实时处理分析的录音场景等,如:会说话的汤姆猫『AppStore | GooglePlay
  • -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

关于音频技术是一门庞大且很专业的学术,这里不会阐述该知识的底层原理知识(比如:声音的原理,音波的正弦平面波合成等等),主要介绍音频相关的一些基本的知识概念,以及在实际开发过程中需要掌握关键API等。

-

声音

-

“声音是振动产生的声波,通过介质气体固体液体)传播并能被人或动物听觉器官所感知的波动现象”。声音的频率一般以赫兹表示,记为Hz,指每秒周期性震动的次数

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

在开发的路上,有时候面对一些应用,我们可能回去分析研究它的实现以及数据交互等,在没有官方没有公开的Api提供时,我们会用到一项实用的技术,抓包,所谓的抓包,指的是截取网络传输发送与接收的数据包。其中在Windows平台上使用比较广泛的要数Fiddler

-

本节主要讲解Fiddler的相关配置及简单使用

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

以前,git的账号只用来在Github上操作,随着积累Git管理的项目不仅仅只来自Github,还有一些其它Git项目托管的平台,例如:BitbucketCodingGiteeGitlib,以及公司内Git仓库

-

不同的托管平台有着不同的Git账号,无法用一个账号来管理其它的仓库,而且由于不同的托管平台账号不同,因此需要添加不同账号的公钥,这样我们再能在对应平台用对应的账号进行操作

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-
-

怕,你就会输一辈子

- -
-

喜欢其中的一些台词,大伙共勉

-
    -
  • 其实,我每次上台都很怕的,不过每次我都会跟自己说,我能做到
  • -
  • 这场比赛我可能会跌倒,但我一定会站起来
  • -
  • 怕,你就会输一辈子
  • -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.(一个在 Java VM 上使用可观测的序列( 观察者模式 )来组成异步的、基于事件的程序的库).

-

在实际开发过程中,RxJava已是一个不可或缺的组件,因此对于RxJava的学习和思考,记录分享是很重要的一个环节

-

本系列文章主要:

-
    -
  1. RxJava 入门
  2. -
  3. RxJava 实际应用
  4. -
  5. RxJava 源码剖析
  6. -
- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

Github 全球最大的同性交友网站,这里拥有最前沿的IT技术创新,拥有最流行的开源项目,等等…,总之这里是我的知识仓库,每天都会在上面寻找,学习知识

-

扯远了,本篇解决对于fork的项目,如何进行源项目的更新和同步问题

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-

之前粗略的接触了Linux的基础使用和安装,这次准备在自购的服务器上跑些应用,纯属娱乐,废话不说,上来就先仍数据库。
-数据库常用的Oracle,MySQL,SQL Server,MongoDB等,排名不分先后,自己平时接触最多的也就是MySQLMongoDB,好MySQL先来一份。

-

介绍

-

MySQL是一个开源数据库管理系统,通常作为流行的LEMP(Linux,Nginx,MySQL / MariaDB,PHP / Python / Perl)堆栈的一部分安装。它使用关系数据库和SQL(结构化查询语言)来管理其数据。

-

CentOS 7更喜欢MariaDB,它是由原始MySQL开发人员管理的MySQL分支,旨在替代MySQL。如果你在CentOS 7上运行 yum install mysql,那么安装的是MariaDB,而不是MySQL

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - - -
- - - -
- - - - - - - -
-

- -

- - -
- - - - -
-
-

工欲善其事,必先利其器

- -
-

记录汇总一些资源库

- -
- - 阅读全文 » - -
- - - -
- - - - - -
-
- -
-
-
- - - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scaffolds/draft.md b/scaffolds/draft.md new file mode 100644 index 000000000..498e95baf --- /dev/null +++ b/scaffolds/draft.md @@ -0,0 +1,4 @@ +--- +title: {{ title }} +tags: +--- diff --git a/scaffolds/page.md b/scaffolds/page.md new file mode 100644 index 000000000..f01ba3cd8 --- /dev/null +++ b/scaffolds/page.md @@ -0,0 +1,4 @@ +--- +title: {{ title }} +date: {{ date }} +--- diff --git a/scaffolds/post.md b/scaffolds/post.md new file mode 100644 index 000000000..1f9b9a465 --- /dev/null +++ b/scaffolds/post.md @@ -0,0 +1,5 @@ +--- +title: {{ title }} +date: {{ date }} +tags: +--- diff --git a/search.xml b/search.xml deleted file mode 100644 index 8b0480ef1..000000000 --- a/search.xml +++ /dev/null @@ -1,11242 +0,0 @@ - - - - Android 音频基础知识 - /2018/10/26/android-audio-base/ - 关于音频技术是一门庞大且很专业的学术,这里不会阐述该知识的底层原理知识(比如:声音的原理,音波的正弦平面波合成等等),主要介绍音频相关的一些基本的知识概念,以及在实际开发过程中需要掌握关键API等。

-

声音

-

“声音是振动产生的声波,通过介质气体固体液体)传播并能被人或动物听觉器官所感知的波动现象”。声音的频率一般以赫兹表示,记为Hz,指每秒周期性震动的次数

- -

trasound_range_diagram

-
-

图片来自Wikipedia

-
-
    -
  • 红:次声波(由火山爆发、龙卷风、雷暴、台风等许多灾害性事件发生前都会产生出次声波,人们就可以利用这种前兆来预报灾害事件的发生)
  • -
  • 蓝:可听声波(20~20000Hz)
  • -
  • 绿:超声波(广泛应用于工业、军事、医疗等行业。在工业上,常用超声波来清洗精密零件,原理是利用超声波在清洗液中产生震荡波,使清洗液产生瞬间的小气泡,从而冲洗零件的每个角落)
  • -
-

音频开发应用场景

-
    -
  • 音频播放器,录音机
  • -
  • 语音电话
  • -
  • 音视频监控
  • -
  • 音视频直播
  • -
  • 音视频编辑/处理软件
  • -
  • 蓝牙耳机/音响等
  • -
-

音频开发具体内容

-
    -
  • 音频采集/播放
  • -
  • 音频算法处理(去噪,静音检测,回声消除,音效处理,功放/增强,混音/分离,等等)
  • -
  • 音频的编解码和格式转换
  • -
  • 音频传输协议的开发(SIPA2DPAVRCP,等等) -
      -
    • SIP(Session Initiation Protocol:会话发起协议):一个由IETF MMUSIC工作组开发的协议,作为标准被提议用于建立,修改和终止包括视频,语音,即时通信,在线游戏和虚拟现实等多种多媒体元素在内的交互式用户会话
    • -
    • A2DP(Advance Audio Distribution Profile:蓝牙立体声音频传输规范):规定了使用蓝牙异步传输信道方式,传输高质量音乐文件数据的协议堆栈软件和使用方法,基于该协议就能通过以蓝牙方式传输高质量的立体声音乐
    • -
    • AVRCP(Audio Video Remote Control Profile:音频/视频远程控制配置文件):用于提供控制 TV、Hi-Fi 设备等的标准接口。此配置文件用于许可单个远程控制设备。
    • -
    -
  • -
-

音频基础知识

-

声音经过麦克风采集后,得到是模拟信号,接着我们需要用程序将采集得到模拟型号,进行转换得到数字信号,这样我们才可以存储,交换等

-
-

关于声音信息得到模拟信号的转换,我们一般是无需关心,设备的麦克风这些都已经帮我们转换好了,我们需要关心的是从麦克风得到的模拟信号,如何去转换为数字信号,最终保存为音频文件

-
-

模拟信号转数字信号

-

模拟信号一般通过PCM(Pulse-code modulation:脉冲编码调制)方法转换为数字信号

-

转换步骤

-
    -
  1. 采样:将一段时间内的连续信号转为离散信号 -
      -
    • 模拟信号本身是一种连续信号,它在一定的时间范围内可以有无限多个不同的取值
    • -
    • 数值信号指在取值上是离散的,不连续的信号
    • -
    -
  2. -
  3. 量化:值采样得到后的数据,我们用多少位的二进制数字来表示声音的振幅
  4. -
  5. 编码:将采样量化后的数据按照一定的格式进行记录
  6. -
-

PCM

-

音频编码最多只能做到无限接近,至少目前的技术只能这样,相对自然界的信号,任何数字音频编码方式都是有损,因为无法完全还原。在计算机应用中,能够达到最高保真的就是PCM编码,因此PCM约定俗成了无损编码(PCM代表了数字音频中最佳的保真水平,并不意味着PCM就能够确保信号绝对保真,PCM也只能做到最大程度的无限接近)

-

经过采集和量化后的声音信号已经是数字形式了,但是为了便于计算机的存储,处理,传输,还必须按照一定的要求进行数据压缩编码

-
压缩
-

一种音频文件格式可以支持多种编码,例如AVI文件格式,但多数的音频文件仅支持一种音频编码

-

主要的音频文件格式:

-
    -
  • 无损格式,例如:WAVFLACAPEALACWavPack(WV)
  • -
  • 有损格式,例如:MP3AACOgg VorbisOpus
  • -
-
编码
-

根据编码方式的不同,音频编码技术分为三种

-
    -
  • 波形编码:音质质量高,编码速率也很高。脉冲编码调变(PCM)、自适应增量调制( ADM )、Adaptive( ADPCM )等都属于该类编码器。
  • -
  • 参数编码:音质质量低,编码速率也很低
  • -
  • 混合编码:音质和速率介于波形编码,参数编码之间
  • -
-
-

为什么音频需要编码

-
-
    -
  1. PCM所量化得到的数据是原始无损的数据,文件很大,不利于传播,存储等
  2. -
  3. 如果都是未压缩的文件,那么基本无法做到差异化即部分需要知识产权保护的组织或机构等
  4. -
-

音频开发中重要参数

-

采样率(samplerate)

-

指每秒从连续信号中提取并组成离散信号的采样个数,也就是1S内,对模拟信号进行多少次采样;采样频率越高,说明采样点之间越密集,记录这段音频所用的数据量就越大,因此音质也就越好

-
-

为什么通用的采样率是44.1kHz?

-
-

量化精度(位宽)

-

用二进位来表示每一个采样值,也称为量化位数,声音信号的量化位数一般是4,6,8,12或16 bits.

-

这个数值的数据类型大小可以是:4bit,8bit,16bit,32bit等等,位数越多,表示的就越精细,声音的质量也就越好,当然文件大小也会成倍增大

-

声道数(channels)

-

由于音频的采集和播放是可以叠加的,因此,可以同时从多个音频源采集声音,并分别输出到不同的扬声器,故声道数一般表示声音录制时的音源数量或回放时相应的扬声器数量

-
    -
  • 单声道(Mono):1
  • -
  • 双声道(Stereo):2
  • -
-

比特率

-

比特率是音频文件每秒占据的字节数(比特数)

-

比特率规定适用“比特每秒”(bit/sbps)为单位,其中ps指的是/s,即每秒。

-

通常我们在音乐播放软件中看到的音乐质量『标准(128kbit/s),较高(198kbit/s),极高(320kbit/s)』表述的即比特率

-

音频帧(frame)

-

视频每一帧就是一张图像,而音频数据是流式,本身没有明确的一帧帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗称2.5ms~60ms为单位的数据量为一帧音频。

-

理论音频的大小

-

假设某通道的音频信号是采样率为8kHz,位宽为16bit,20ms一帧,双通道,则一帧音频数据的大小为:

-
# 一帧音频的大小
int size = 8000 x 16bit x 0.02s x 2 = 5120 bit = 640 byte;
-

音频处理开源项目

-

VoIP相关

-

基于IP的语音传输(英语:Voice over Internet Protocol,缩写为VoIP)是一种语音通话技术,经由网际协议(IP)来达成语音通话与多媒体会议,也就是经由互联网来进行通信。其他非正式的名称有IP电话(IP telephony)、互联网电话(Internet telephony)、宽带电话(broadband telephony)以及宽带电话服务(broadband phone service)。

-
    -
  • imsdroid
  • -
  • sipdroid
  • -
  • csipsimple
  • -
  • linphone
  • -
  • WebRTC
  • -
-

算法相关

-
    -
  • ffmpeg
  • -
  • speex
  • -
-

其他

-

MP3编码库

-
    -
  • Lame
  • -
-

Android提供相关API

-
    -
  • 音频采集:MediaRecoder,AudioRecord
  • -
  • 音频播放:SoundPool,MediaPlayer,AudioTrack
  • -
  • 音频编解码:MediaCodec
  • -
  • NDK API:OpenSL ES
  • -
-

附录

-
    -
  • 语音编码
  • -
  • 高级音频编码 ● AAC
  • -
  • 音频技术可以延展众多应用场景
  • -
-]]>
- - Android - - - media - -
- - Android 音频录制与播放 - /2018/10/27/android-audio/ - 上一篇主要介绍了音频相关的一些基础知识,本篇主要介绍在Android系统中如何进行音频的录制,播放

-

音频录制

-

Android SDK中提供了AudioRecordMediaRecorder两个API经行音频的录制,具体的优缺点等如下:

-
    -
  • AudioRecord 『added in API level 3』(基于字节流录音):
    -优点:可以实现语音的实时处理,进行边录边播,对音频的实时处理。
    -缺点:输出的是PCM的语音数据,如果保存成音频文件是不能被播放器播放的。要用到这个去进行处理。
    -适用场景:需要实时处理分析的录音场景等,如:会说话的汤姆猫『AppStore | GooglePlay
  • -
- -
    -
  • MediaRecorder 『added in API level 1』(基于文件音视频录制):
    -优点:封装度很高,操作简单,无需处理中间录制过程;录制的音频文件是经过压缩的,需要设置编码器;录制的音频文件可以使用系统自带的播放器播放
    -缺点:无法实现实时处理音频,输出的音频格式少。
    -适用场景:录制过程需要实时处理的场景等
  • -
-

音频播放

-
    -
  • -

    AudioTrack『added in API level 3』:
    -AudioTrack 则更接近底层,提供了非常强大的控制能力,支持低延迟播放,适合流媒体和VoIP语音电话等场景

    -
  • -
  • -

    SoundPool 『added in API level 1』:
    -优点:主要用于播放一些较短的声音片段,支持从程序的资源或文件系统加载;CPU的资源占用量低、反应延迟小,并且可以加载多个音频到SoundPool中,通过资源ID来管理
    -缺点:SoundPool加载资源,最大只能申请 1MB 的内存控件,因此只能用来播放一些很短的声音片段
    -适用场景:播放短,反应要求高的音频

    -
  • -
  • -

    MediaPlayer 『added in API level 1』(基于字节流音视频播放):
    -优点:支持本地,网络音频资源的播放
    -缺点:资源占用量较高、加载延迟时间较长;不支持多个音频同时播放等
    -适用场景:播放长音频

    -
  • -
-
-

Google官方给出了兼容支持

-
-

AudioRecord

-

录制流程

-
    -
  1. 构造一个AudioRecord对象,其中需要的最小音频缓存buffer大小可以通过getMinBufferSize()方法得到,如果buffer容量过小,将导致对象构造失败
  2. -
  3. 初始化一个buffer,该buffer 大于等于AudioRecord对象用于写音频数据的buffer大小
  4. -
  5. 开始录音
  6. -
  7. 创建一个数据流,一边从AudioRecord中读取音频数据到初始化的buffer,一边将buffer中的数据导入数据流
  8. -
  9. 关闭数据流
  10. -
  11. 停止录音
  12. -
-

参数配置

-
    -
  • audioSource :音频采集的输入源 -
      -
    • DEFAULT(默认)
    • -
    • VOICE_RECOGNITION(用于语音识别,等同于DEFAULT)
    • -
    • MIC(由手机麦克风输入)
    • -
    • VOICE_COMMUNICATION(用于VoIP应用)
    • -
    -
  • -
  • sampleRateInHz:采样率
    -目前44100Hz是唯一可以保证兼容所有Android手机的采样率
  • -
  • channelConfig:通道数的配置 -
      -
    • CHANNEL_IN_MONO:单通道
    • -
    • CHANNEL_IN_STEREO:双通道
    • -
    -
  • -
  • audioFormat:数据位宽 -
      -
    • ENCODING_PCM_8BIT:8bit
    • -
    • ENCODING_PCM_16BIT:16bit
    • -
    -
  • -
  • bufferSizeInBytes:AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小
  • -
-

示例代码

-
public class AudioCapturer {

private static final String TAG = "AudioCapturer";

private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC;
private static final int DEFAULT_SAMPLE_RATE = 44100;
private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;

private AudioRecord mAudioRecord;
private int mMinBufferSize = 0;

private Thread mCaptureThread;
private boolean mIsCaptureStarted = false;
private volatile boolean mIsLoopExit = false;

private OnAudioFrameCapturedListener mAudioFrameCapturedListener;

public interface OnAudioFrameCapturedListener {
public void onAudioFrameCaptured(byte[] audioData);
}

public boolean isCaptureStarted() {
return mIsCaptureStarted;
}

public void setOnAudioFrameCapturedListener(OnAudioFrameCapturedListener listener) {
mAudioFrameCapturedListener = listener;
}

public boolean startCapture() {
return startCapture(DEFAULT_SOURCE, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG,
DEFAULT_AUDIO_FORMAT);
}

public boolean startCapture(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {

if (mIsCaptureStarted) {
Log.e(TAG, "Capture already started !");
return false;
}

mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
Log.e(TAG, "Invalid parameter !");
return false;
}
Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");

mAudioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize);
if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
Log.e(TAG, "AudioRecord initialize fail !");
return false;
}

mAudioRecord.startRecording();

mIsLoopExit = false;
mCaptureThread = new Thread(new AudioCaptureRunnable());
mCaptureThread.start();

mIsCaptureStarted = true;

Log.d(TAG, "Start audio capture success !");

return true;
}

public void stopCapture() {

if (!mIsCaptureStarted) {
return;
}

mIsLoopExit = true;
try {
mCaptureThread.interrupt();
mCaptureThread.join(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}

if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
mAudioRecord.stop();
}

mAudioRecord.release();

mIsCaptureStarted = false;
mAudioFrameCapturedListener = null;

Log.d(TAG, "Stop audio capture success !");
}

private class AudioCaptureRunnable implements Runnable {

@Override
public void run() {

while (!mIsLoopExit) {

byte[] buffer = new byte[mMinBufferSize];

int ret = mAudioRecord.read(buffer, 0, mMinBufferSize);
if (ret == AudioRecord.ERROR_INVALID_OPERATION) {
Log.e(TAG , "Error ERROR_INVALID_OPERATION");
}
else if (ret == AudioRecord.ERROR_BAD_VALUE) {
Log.e(TAG , "Error ERROR_BAD_VALUE");
}
else {
if (mAudioFrameCapturedListener != null) {
mAudioFrameCapturedListener.onAudioFrameCaptured(buffer);
}
Log.d(TAG , "OK, Captured "+ret+" bytes !");
}
}
}
}
}
-

AudioTrack

-

播放流程

-
    -
  1. 配置参数,初始化内部的音频播放缓冲区到,如果buffer容量过小,将导致对象构造失败
  2. -
  3. 开始播放
  4. -
  5. 需要一个线程,不断地向 AudioTrack 的缓冲区写入音频数据,注意,这个过程一定要及时,否则就会出现underrun的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“送入”音频数据,导致内部的音频播放缓冲区为空
  6. -
  7. 停止播放,释放资源
  8. -
-

参数配置

-
    -
  • streamType:当前应用使用的哪一种音频管理策略
    -当系统有多个进程需要播放音频时,这个管理策略会决定最终的展现效果 -
      -
    • STREAM_VOCIE_CALL:电话声音
    • -
    • STREAM_SYSTEM:系统声音
    • -
    • STREAM_RING:铃声
    • -
    • STREAM_MUSCI:音乐声
    • -
    • STREAM_ALARM:警告声
    • -
    • STREAM_NOTIFICATION:通知声
    • -
    -
  • -
  • sampleRateInHz:采样率
    -采样率的取值范围必须在 4000Hz~192000Hz 之间
  • -
  • channelConfig:通道数的配置 -
      -
    • CHANNEL_IN_MONO:单通道
    • -
    • CHANNEL_IN_STEREO:双通道
    • -
    -
  • -
  • audioFormat:数据位宽 -
      -
    • ENCODING_PCM_8BIT:8bit
    • -
    • ENCODING_PCM_16BIT:16bit
    • -
    -
  • -
  • bufferSizeInBytes:配置的是 AudioTrack 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小
  • -
  • mode:AudioTrack 播放模式 -
      -
    • MODE_STATIC
      -static:一次性将所有的数据都写入播放缓冲区,简单高效,通常用于播放铃声、系统提醒的音频片段
    • -
    • MODE_STREAM
      -streaming:按照一定的时间间隔不间断地写入音频数据,理论上它可用于任何音频播放的场景
    • -
    -
  • -
-

示例代码

-
public class AudioPlayer {

private static final String TAG = "AudioPlayer";

private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC;
private static final int DEFAULT_SAMPLE_RATE = 44100;
private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private static final int DEFAULT_PLAY_MODE = AudioTrack.MODE_STREAM;

private boolean mIsPlayStarted = false;
private int mMinBufferSize = 0;
private AudioTrack mAudioTrack;

public boolean startPlayer() {
return startPlayer(DEFAULT_STREAM_TYPE,DEFAULT_SAMPLE_RATE,DEFAULT_CHANNEL_CONFIG,DEFAULT_AUDIO_FORMAT);
}

public boolean startPlayer(int streamType, int sampleRateInHz, int channelConfig, int audioFormat) {

if (mIsPlayStarted) {
Log.e(TAG, "Player already started !");
return false;
}

mMinBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
if (mMinBufferSize == AudioTrack.ERROR_BAD_VALUE) {
Log.e(TAG, "Invalid parameter !");
return false;
}
Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");

mAudioTrack = new AudioTrack(streamType,sampleRateInHz,
channelConfig,audioFormat,mMinBufferSize,DEFAULT_PLAY_MODE);

if (mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED) {
Log.e(TAG, "AudioTrack initialize fail !");
return false;
}

mIsPlayStarted = true;

Log.d(TAG, "Start audio player success !");

return true;
}

public int getMinBufferSize() {
return mMinBufferSize;
}

public void stopPlayer() {

if (!mIsPlayStarted) {
return;
}

if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
mAudioTrack.stop();
}

mAudioTrack.release();
mIsPlayStarted = false;

Log.d(TAG, "Stop audio player success !");
}

public boolean play(byte[] audioData, int offsetInBytes, int sizeInBytes) {

if (!mIsPlayStarted) {
Log.e(TAG, "Player not started !");
return false;
}

if (sizeInBytes < mMinBufferSize) {
Log.e(TAG, "audio data is not enough !");
return false;
}

if (mAudioTrack.write(audioData,offsetInBytes,sizeInBytes) != sizeInBytes) {
Log.e(TAG, "Could not write all the samples to the audio device !");
}

mAudioTrack.play();

Log.d(TAG , "OK, Played "+sizeInBytes+" bytes !");

return true;
}
}
-

MediaRecorder

-

mediarecorder

-

如上所示表述整个MediaRecorder的整个生命过程,可以看出初始化之后,在任意的状态下调用reset()方法均可以回到MediaRecorder刚刚初始化完成的状态

-

MediaPlayer

-

mediaplayer

-

MediaPlayer 工作流程

-
    -
  1. 创建一个MediaPlayer对象
  2. -
  3. 调用setDataSource()方法,设置音频文件的路径
  4. -
  5. 接着调用prepare()方法,使MediaPlayer进入的准备状态
  6. -
  7. 调用start()方法,开始播放音频『pause()方法表示:暂停播放』
  8. -
-

MediaPlayer常用的控制方法

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
方法名功能描述
setDataSource()设置要播放的音频文件的位置
prepare()在开始播放之前调用这个方法完成准备工作
start()开始或继续播放音频
pause()暂停播放音频
reset()将MediaPlayer对象重置到刚刚创建的状态
seekTo()从指定位置开始播放音频
stop()停止播放音频。调用这个方法后的MediaPlayer对象无法再播放音频
release()释放掉与MediaPlayer对象相关的资源
isPlaying()判断当前MediaPlayer是否正在播放音频
getDuration()获取站如的音频文件的时长
-

注意事项

-
    -
  1. 在使用star()播放流媒体之前,需要装载流媒体资源。这里最好使用prepareAsync()异步的方式装载流媒体资源,在使用prepareAsync()异步加载时,为避免还没有装载完就调用了start()而保存,需要绑定MediaPlayer.setOnPreparedListener()事件,它将在异步装在完成后回调
    -原因:流媒体资源的装载是会消耗系统资源,在一些硬件不理想的设备上,如果使用prepare()同步的方式装载资源,可能会造成UI界面卡顿,其次避免装载超时而引发ANR等问题
  2. -
  3. 使用完MediaPlayer需要回收资源。MediaPlayer时很消耗系统资源的,所以在使用完MediaPlayer,及时主动回收资源
  4. -
  5. 对于单曲循环之类的操作,除了使用setLooping()方法设置之外,还可以为MediaPlayer注册回调函数,MediaPlayer.setOnCompletionListener(),它会在MediaPlayer播放完被回调
  6. -
  7. 由于无法确保播放的流媒体是完整(中间有错误),我们需要处理这个错误,否则会影响用户体验。可以在MediaPlayer中注册setOnErrorListener()错误回调,一般重新播放或者播放下一个流媒体
  8. -
-

跨平台

-

关于音频编解码在各平台上的情况如下
-wiki-ecode

-

从上图可知,AACFLACMP3三种编码是全平台支持的音频编码方式(或音频压缩方式),注意编码方式并不是文件格式即文件的扩展名

-
    -
  • AAC 主要扩展名 -
      -
    • .aac
    • -
    • .mp4
    • -
    • .m4a
    • -
    -
  • -
  • FLAC 扩展名 -
      -
    • .flac
    • -
    -
  • -
  • MP3 扩展名 -
      -
    • .mp3
    • -
    -
  • -
-

总结

-
    -
  • 音频的录制,Android SDK提供了两套音频采集的API,分别是:MediaRecorderAudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如:AMR,OGG等)并存储成文件,而后者则更接近底层,能够更加自由灵活的控制,可以得到原始的一帧帧PCM音频数据
  • -
  • 如果要简单的进行音频的采集,录制成音频文件,则推荐适用MediaRecorder,而如果需要对音频做进一步的算法处理,或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议适用AudioRecord
  • -
  • MediaRecorder底层的实现也是调用了AudioRecordAndroid Framework 层的AudioFlinger进行交互
  • -
-
-

关于音视频相关的资料参差不齐,目前尚未有大量相关专门的书籍来介绍该领域的图书或者易懂视频,很多情况需要根据所处应用场景灵活应变。
-推荐刚刚发行的一本关于音频方面的图书《Android音视频开发》
-推荐国内比较专业音视频方面相关的介绍《雷霄骅的专栏》

-
-

附录

-
    -
  • 音频编码格式的比较
  • -
  • 第一行代码
  • -
  • Android MediaRecorder架构详解
  • -
  • 参考代码
  • -
  • 浏览器引擎
  • -
  • 主流浏览器内核介绍
  • -
  • 腾讯X5内核介绍
  • -
  • Android 音视频开发学习思路
  • -
-]]>
- - Android - - - media - -
- - Android 定位知多少(一) - /2019/05/26/android-location1/ - 手机行业持续不断发展,为我们生活带了很多便利,在我们生活中到处都存在它的痕迹,它不仅是一个工具而且还是有温度的组手,协助你解决生活中的各种问题,渐渐成为了人们不可或缺的“器官”。它为什么就能进化成人类的一部分呢?其中一个重要的功能就是定位,看似单一的功能却渗透了我们各种场景,比如:定位,导航,这种基础的功能,还基于定位社交聊天,运动轨迹画像,出行等等,解决了人与人,人与物,物与物之间在位置上的问题。那么我本节就来聊一聊定位相关的一些知识,以及手机是如何在 Android 系统中是如何进行定位的

- -

在 Android 系统中,定位主要分为两类:

-
    -
  • 硬件定位(GPS 定位,北斗定位 ……)
  • -
  • 网络定位 -
      -
    • 基站定位
    • -
    • WiFi 定位
    • -
    -
  • -
-

参考

-
    -
  1. 用户位置
  2. -
-]]>
- - Android - - - Location - -
- - Android 定位知多少(二) - /2019/05/26/android-location2/ - 本篇主要讲解定位策略

-]]>
- - Android - - - Location - -
- - Android XML字符串 - /2019/10/27/android-string/ - Android在开发过程中,一些特殊字符时无法直接在 strings.xml 文件中写,需要用对应的转义字符代替或者在特殊符号(比如:´" 等待)前添加 \ ,比如一个 TextView 控件中,需要动态替换其中的一些数据,再比如需要调整 TextView 字体的一些HTML样式(比如:粗体,斜体,下划线等),虽然这些都可以用 TextView 去修改,但更简单的方法是设置string提供的属性即可

- -

特殊字符

-
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--
无法直接使用I'm developer
<string name="name_introduce">I'm developer</string>
-->
<!-- 解决方法一 转义字符代替 -->
<string name="name_introduce">I&#039;m developer</string>
<!-- 解决方法二 使用 \ -->
<string name="name_introduces">I\'m developer</string>
</resources>
-

动态替换或拼接

-
    -
  • %n$ms:代表输出的是字符串,n代表是第几个参数,设置m的值可以在输出之前放置空格
  • -
  • %n$md:代表输出的是整数,n代表是第几个参数,设置m的值可以在输出之前放置空格
  • -
  • %n$mf:代表输出的是浮点数,n代表第几个参数,m在浮点类型之前放置几个空格
  • -
-

XML配置

-
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
%1$s 第一个参数,对应Jerry
%2$d 第二个参数,对应36
%3$4.2f 第三个参数,对应 195.1255 ,但保留两位小数,实际显示195.13
-->
<string name="welcome_messages">Hello, %1$s, You have %2$d new messages. total cost %3$4.2f</string>
</resources>
-

Java设置

-
mTextConent = (TextView) findViewById(R.id.tv_String);
mTextConent.setText(String.format(getString(R.string.welcome_messages), "Jerry", 36, 195.1255));
-

HTML标记

-
    -
  • <b> 表示 粗体 文本。
  • -
  • <i> 表示 斜体 文本。
  • -
  • <u> 表示 下划线 文本。
  • -
-
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="welcome">Welcome to <b>Android</b>!</string>
<string name="android">Welcome to <i>Android</i>!</string>
<string name="hello">Welcome to <u>Android</u>!</string>
</resources>
-

ASCII对照表

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ASCII码符号ASCII码符号ASCII码符号ASCII码符号
&#064;@&#058;:&#160;空格&#032;空格
&#033;!&#034;"&#035;#&#036;$
&#037;%&#038;&&#039;´&#040;(
&#042; *&#043;+&#044;,&#041;)
&#045;-&#046;.&#047;/&#058;:
&#059;;&#060;<&#061;=&#062;>
&#063;?&#064;@&#091;[&#092;>
&#093;]&#094;^&#095;_&#096;`
&#123;{&#124; |&#125;}&#126;~
&#160;(空格,在xml首字符中不会被忽略)&#161;¡&#162;¢&#163;£
&#164;¤&#165;¥&#166;¦&#167;§
&#168;¨&#169;©&#170;ª&#171;«
&#172;¬&#173;&#174;®&#175;¯
&#176;°&#177;±&#178;²&#179;³
&#180;´&#181;µ&#182;&#183;
&#184;¸&#185;¹&#186;º&#187;»
&#188;¼&#189;½&#190;¾&#191;¿
&#192;À&#193;Á&#194;Â&#195;Ã
&#196;Ä&#197;Å&#198;Æ&#199;Ç
&#200;È&#201;É&#202;Ê&#203;Ë
&#204;Ì&#205;Í&#206;Î&#207;Ï
&#208;Ð&#209;Ñ&#210;Ò&#211;Ó
&#212;Ô&#213;Õ&#214;Ö&#215;×
&#216;Ø&#217;Ù&#218;Ú&#219;Û
&#220;Ü&#221;Ý&#222;Þ&#223;ß
&#224;à&#225;á&#226;â&#227;ã
&#228;ä&#229;å&#230;æ&#231;ç
&#232;è&#233;é&#234;ê&#235;ë
&#236;ì&#237;í&#238;î&#239;ï
&#240;ð&#241;ñ&#242;ò&#243;ó
&#244;ô&#245;õ&#246;ö&#247;÷
&#248;ø&#249;ù&#250;ú&#251;û
&#252;ü&#253;ý&#254;þ&#255;ÿ
&#256;Ā&#257;ā&#258;Ă&#259;ă
&#260;Ą&#261;ą&#262;Ć&#263;ć
&#264;Ĉ&#265;ĉ&#266;Ċ&#267;ċ
&#268;Č&#269;č&#270;Ď&#271;ď
&#272;Đ&#273;đ&#274;Ē&#275;ē
&#276;Ĕ&#277;ĕ&#278;Ė&#279;ė
&#280;Ę&#281;ę&#282;Ě&#283;ě
&#284;Ĝ&#285;ĝ&#286;Ğ&#287;ğ
&#288;Ġ&#289;ġ&#290;Ģ&#291;ģ
&#292;Ĥ&#293;ĥ&#294;Ħ&#295;ħ
&#296;Ĩ&#297;ĩ&#298;Ī&#299;ī
&#300;Ĭ&#301;ĭ&#302;Į&#303;į
&#304;İ&#305;ı&#306;IJ&#307;ij
&#308;Ĵ&#309;ĵ&#310;Ķ&#311;ķ
&#312;ĸ&#313;Ĺ&#314;ĺ&#315;Ļ
&#316;ļ&#317;Ľ&#318;ľ&#319;Ŀ
&#320;ŀ&#321;Ł&#322;ł&#323;Ń
&#324;ń&#325;Ņ&#326;ņ&#327;Ň
&#328;ň&#329;ʼn&#330;Ŋ&#331;ŋ
&#332;Ō&#333;ō&#334;Ŏ&#335;ŏ
&#336;Ő&#337;ő&#338;Œ&#339;œ
&#340;Ŕ&#341;ŕ&#342;Ŗ&#343;ŗ
&#344;Ř&#345;ř&#346;Ś&#347;ś
&#348;Ŝ&#349;ŝ&#350;Ş&#351;ş
&#352;Š&#353;š&#354;Ţ&#355;ţ
&#356;Ť&#357;ť&#358;Ŧ&#359;ŧ
&#360;Ũ&#361;ũ&#362;Ū&#363;ū
&#364;Ŭ&#365;ŭ&#366;Ů&#367;ů
&#368;Ű&#369;ű&#370;Ų&#371;ų
&#372;Ŵ&#373;ŵ&#374;Ŷ&#375;ŷ
&#376;Ÿ&#377;Ź&#378;ź&#379;Ż
&#380;ż&#381;Ž&#382;ž
-

附录

-
    -
  • 字符串资源
  • -
-]]>
- - Android - - - Util - -
- - BigDecimal - /2019/10/20/bigdecimal/ - float 和 double 同样也是可以表示浮点数,为啥在对于要求精确的进度计算时,尤其是关于币值相关,都采用 BigDecimal 类型来处理?

-
    -
  1. float 和 double 类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的 快速近似 计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该用于要求精确结果的场合。——《Effective Java》
  2. -
  3. float 精度 7 位,double 精度 16 位
  4. -
- -

综上所述,对于精确计算时,就不能采用 float 和 double 来计算了,而是 正确的使用 BigDecimal 时,结果才是精确的,为什么会这么说,那就跟着我一块来深入了解 BigDecimal,我们查看java.math路径下,除了 BigDecimal 还有 BigInteger ,因此,我们先去了解 BigInteger

-

BigInteger

-

Java 中,由 CPU 原生提供的整形最大范围是 64 位long类型整数。使用long类型整数可以直接通过 CPU 指令进行计算,速度非常快。如果使用的整数范围超过了long类型的范围怎么办?这时就只能用软件来模拟一个大整数。BigInteger表示不可变的任意精度的整数(继承Number)。BigInteger 内部用一个 int[] 数组来模拟一个非常大的整数

-

构造方法

-
    -
  • BigInteger(byte[] val):将包含 BigInteger 的二进制补码表示形式的 byte 数组转换为 BigInteger
  • -
  • BigInteger(int signum, byte[] magnitude):将 BigInteger 的符号-数量表示形式转换为 BigInteger。
  • -
  • BigInteger(int bitLength, int certainty, Random rnd):构造一个随机生成的正 BigInteger,它可能是一个具有指定 bitLength 的素数
  • -
  • BigInteger(int numBits, Random rnd):构造一个随机生成的 BigInteger,它是在 0 到 (2numBits - 1)(包括)范围内均匀分布的值
  • -
  • BigInteger(String val):将 BigInteger 的十进制字符串表示形式转换为 BigInteger,常用构造方法
  • -
  • BigInteger(String val, int radix):将指定基数的 BigInteger 的字符串表示形式转换为 BigInteger
  • -
-
BigInteger bi = new BigInteger("1234567890");
// 计算出 bi⁵ = 2867971860299718107233761438093672048294900000
System.out.println(bi.pow(5));
-

常用运算方法

-

对于加减乘除等运算,BigInteger 提供了对应的方法

-
BigInteger a = new BigInteger("1234567890");
BigInteger b = new BigInteger("9876543210");
// a+b = 11111111100
System.out.println("a+b = " + a.add(b));
// a-b = -8641975320
System.out.println("a-b = " + a.subtract(b));
// a*b = 12193263111263526900
System.out.println("a*b = " + a.multiply(b));
// a/b = 0
System.out.println("a/b = " + a.divide(b));
-

转换

-

long 类型整数运算比,BigInteger不会有范围限制,但缺点是速度比较慢。

-
BigInteger i = new BigInteger("123456789000");
// 123456789000
System.out.println(i.longValue());
// java.lang.ArithmeticException: BigInteger out of long range
System.out.println(i.multiply(i).longValueExact());
-
-

使用 longValueExact() 方法时,如果超出了 long 类型的范围,会抛出 ArithmeticException

-
-

BigIntegerIntegerLong 一样,也是不可变类,并且也继承自 Number 类。因为 Number 定义了转换为基本类型的几个方法:

-
    -
  • 转换为bytebyteValue()
  • -
  • 转换为shortshortValue()
  • -
  • 转换为intintValue()
  • -
  • 转换为longlongValue()
  • -
  • 转换为floatfloatValue()
  • -
  • 转换为doubledoubleValue()
  • -
-

通过上述方法,可以把 BigInteger 转换为基本类型。如果 BigInteger 表示的范围超过了基本类型,转换时将丢失高位信息,即结果不一定准确;因此,如果需要 准确的转换成基本类型,可以使用 intValueExact()longValueExact() 等方法,在转换时如果超出范围,将直接抛出 ArithmeticException异常

-

BigDecimal

-

BigDecimalBigInteger类似,BigDecimal 表示一个任意大小且精度完全准确的浮点数。BigDecimal 是由任意精度的整数非标度值(unscaled value)和 32 位的整数标度(scale)组成,通常用于币值的计算。

-

构造方法

-

BigDecimal 拥有16 个构造方法,常用如下三种

-
    -
  • BigDecimal BigDecimal(double d); // 不允许使用,精度不能保证
  • -
  • BigDecimal BigDecimal(String s); // 常用,推荐使用
  • -
  • static BigDecimal valueOf(double d); // 常用,推荐使用
  • -
-
BigDecimal bigDecimal = new BigDecimal(2);
BigDecimal bString = new BigDecimal("2.3");
BigDecimal bDouble = new BigDecimal(2.3);
BigDecimal bDouble1 = BigDecimal.valueOf(2.3);
// 输出:bigDecimal = 2
System.out.println("bigDecimal = " + bigDecimal);
// 输出:bString = 2.3
System.out.println("bString = " + bString);
// 输出:bDouble = 2.29999999999999982236431605997495353221893310546875
System.out.println("bDouble = " + bDouble);
// 输出:bDouble1 = 2.3
System.out.println("bDouble1 = " + bDouble1);
-
    -
  • 参数类型为 double 的构造方法的结果有一定的不可预知性;
  • -
  • 参数类型为 String 的构造方法的结果是完全可预知的,因此我们在编写时尽量都用 String 的构造方法
  • -
  • 当 double 必须用作 BigDecimal 的源时可以用 BigDecimal 的静态方法 value()
  • -
-

常用方法

-
BigDecimal a = new BigDecimal("1234567890.56789");
BigDecimal b = new BigDecimal("9876543210.01234");
// a+b = 11111111100.58023
System.out.println("a+b = " + a.add(b));
// a-b = -8641975319.44445
System.out.println("a-b = " + a.subtract(b));
// a*b = 12193263116887551591.2965077626
System.out.println("a*b = " + a.multiply(b));
// 报错:ArithmeticException,因为除不尽
// System.out.println("a/b = " + a.divide(b));
// 保留10位小数并四舍五入
System.out.println("a/b = " + a.divide(b, 10, RoundingMode.HALF_UP));
-

转换

-

BigInteger相同

-

舍入模式

-
    -
  • ROUND_CEILING:向 正无限大方向舍入 的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.UP;如果结果为负,则舍入行为类似于 RoundingMode.DOWN
    5.5  =>  6 
    1.1 => 2
    -1.0 => -1
    -2.5 => -2
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("ROUND_CEILING模式:" + a1.setScale(0, RoundingMode.CEILING));
    System.out.println("ROUND_CEILING模式:" + a2.setScale(0, RoundingMode.CEILING));
    System.out.println("ROUND_CEILING模式:" + a3.setScale(0, RoundingMode.CEILING));
    System.out.println("ROUND_CEILING模式:" + a4.setScale(0, RoundingMode.CEILING));
    -
  • -
  • RoundingMode.DOWN:向 零方向舍入 的舍入模式。从不对舍弃部分前面的数字加 1(即截尾)。注意,此舍入模式始终不会增加计算值的绝对值
    5.5  =>  5 
    1.1 => 1
    -1.0 => -1
    -2.5 => -2
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("DOWN模式:" + a1.setScale(0, RoundingMode.DOWN));
    System.out.println("DOWN模式:" + a2.setScale(0, RoundingMode.DOWN));
    System.out.println("DOWN模式:" + a3.setScale(0, RoundingMode.DOWN));
    System.out.println("DOWN模式:" + a4.setScale(0, RoundingMode.DOWN));
    -
  • -
  • RoundingMode.FLOOR(此舍入模式始终不会增加计算值):向 负无限大方向舍入 的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.DOWN;如果结果为负,则舍入行为类似于 RoundingMode.UP
    5.5  =>  5 
    1.1 => 1
    -1.0 => -1
    -2.5 => -3
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("FLOOR模式:" + a1.setScale(0, RoundingMode.FLOOR));
    System.out.println("FLOOR模式:" + a2.setScale(0, RoundingMode.FLOOR));
    System.out.println("FLOOR模式:" + a3.setScale(0, RoundingMode.FLOOR));
    System.out.println("FLOOR模式:" + a4.setScale(0, RoundingMode.FLOOR));
    -
  • -
  • RoundingMode.HALF_DOWN:向 最接近数字方向舍入 的舍入模式,如果与两个相邻数字的距离相等,则向 下舍入 。如果被舍弃部分 > 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN
    5.5  =>  5
    1.1 => 1
    -1.1 => -1
    -2.5 => -2
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("HALF_DOWN模式:" + a1.setScale(0, RoundingMode.HALF_DOWN));
    System.out.println("HALF_DOWN模式:" + a2.setScale(0, RoundingMode.HALF_DOWN));
    System.out.println("HALF_DOWN模式:" + a3.setScale(0, RoundingMode.HALF_DOWN));
    System.out.println("HALF_DOWN模式:" + a4.setScale(0, RoundingMode.HALF_DOWN));
    -
  • -
  • RoundingMode.HALF_EVEN:向 最接近数字方向舍入 的舍入模式,如果与两个相邻数字的距离相等,则向 相邻的偶数舍入 。如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;如果为偶数,则舍入行为同 RoundingMode.HALF_DOWN
    5.5  =>  6 
    1.1 => 1
    -1.0 => -1
    -2.5 => -2
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("HALF_EVEN模式:" + a1.setScale(0, RoundingMode.HALF_EVEN));
    System.out.println("HALF_EVEN模式:" + a2.setScale(0, RoundingMode.HALF_EVEN));
    System.out.println("HALF_EVEN模式:" + a3.setScale(0, RoundingMode.HALF_EVEN));
    System.out.println("HALF_EVEN模式:" + a4.setScale(0, RoundingMode.HALF_EVEN));
    -
  • -
  • RoundingMode.HALF_UP(此舍入模式就是通常学校里讲的四舍五入):向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。如果被舍弃部分 >= 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN
    5.5  =>  6
    1.1 => 1
    -1.1 => -1
    -2.5 => -3
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.1");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("HALF_UP模式:" + a1.setScale(0, RoundingMode.HALF_UP));
    System.out.println("HALF_UP模式:" + a2.setScale(0, RoundingMode.HALF_UP));
    System.out.println("HALF_UP模式:" + a3.setScale(0, RoundingMode.HALF_UP));
    System.out.println("HALF_UP模式:" + a4.setScale(0, RoundingMode.HALF_UP));
    -
  • -
  • RoundingMode.UNNECESSARY:用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。如果对生成精确结果的操作指定此舍入模式,则抛出 ArithmeticException
    1.5  =>  抛出 ArithmeticException
    1.1 => 抛出 ArithmeticException
    1.0 => 1
    -1.1 =>抛出 ArithmeticException
    -1.6 => 抛出 ArithmeticException
    -
  • -
  • RoundingMode.UP:远离零方向舍入 的舍入模式。始终对非零舍弃部分前面的数字加 1。注意,此舍入模式始终不会减少计算值的绝对值
    5.5  =>  6 
    1.1 => 2
    -1.0 => -1
    -2.5 => -3
    BigDecimal a1 = new BigDecimal("5.5");
    BigDecimal a2 = new BigDecimal("1.1");
    BigDecimal a3 = new BigDecimal("-1.0");
    BigDecimal a4 = new BigDecimal("-2.5");
    System.out.println("UP模式:" + a1.setScale(0, RoundingMode.UP));
    System.out.println("UP模式:" + a2.setScale(0, RoundingMode.UP));
    System.out.println("UP模式:" + a3.setScale(0, RoundingMode.UP));
    System.out.println("UP模式:" + a4.setScale(0, RoundingMode.UP));
    -
  • -
-

格式化

-

DecimalFormat 解析

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
符号位置描叙
0数字阿拉伯数字,如果不存在则显示0
#数字阿拉伯数字,如果不存在不显示0
.数字小数分隔符或货币小数分隔符
,数字分组分隔符
E数字分隔科学计数法中的尾数和指数。在前缀或后缀中无需加引号
-数字负号
;子模式边界分隔正数和负数子模式
%子模式边界乘以 100 并显示为百分数
\u2030子模式边界乘以 1000 并显示为千分数
¤(\u00A4)子模式边界货币记号,由货币符号替换。如果两个同时出现,则用国际货币符号替换。如果出现在某个模式中,则使用货币小数分隔符,而不使用小数分隔符
子模式边界用于在前缀或或后缀中为特殊字符加引号,例如 “‘#’#“将 123 格式化为 “#123”。要创建单引号本身,请连续使用两个单引号:”# o’'clock”
-
// 建立货币格式化引用
NumberFormat currency = NumberFormat.getCurrencyInstance();
// 建立百分比格式化引用
NumberFormat percent = NumberFormat.getPercentInstance();
// 百分比小数点最多3位
percent.setMaximumFractionDigits(3);
BigDecimal loanAmount = new BigDecimal("150.48");
BigDecimal interestRate = new BigDecimal("0.008");
BigDecimal interest = loanAmount.multiply(interestRate);
// 贷款金额: ¥150.48
System.out.println("贷款金额:\t" + currency.format(loanAmount));
// 利率: 0.8%
System.out.println("利率:\t" + percent.format(interestRate));
// 利息: ¥1.20
System.out.println("利息:\t" + currency.format(interest));

//===============================================================
DecimalFormat df = new DecimalFormat();
// 格式化之前的数字
double data = 1234.56789;

// 1.定义要显示的数字的格式(这种方式会四舍五入)
String style = "0.0";
df.applyPattern(style);
// 1-->1234.6
System.out.println("1-->" + df.format(data));

// 2.在格式后添加诸如单位等字符
style = "00000.000 kg";
df.applyPattern(style);
// 2-->01234.568 kg
System.out.println("2-->" + df.format(data));

// 3.模式中的"#"表示如果该位存在字符,则显示字符,如果不存在,则不显示。
style = "##000.000 kg";
df.applyPattern(style);
// 3-->1234.568 kg
System.out.println("3-->" + df.format(data));

// 4.模式中的"-"表示输出为负数,要放在最前面
style = "-000.000";
df.applyPattern(style);
// 4-->-1234.568
System.out.println("4-->" + df.format(data));

// 5.模式中的","在数字中添加逗号,方便读数字
style = "-0,000.0#";
df.applyPattern(style);
// 5-->-1,234.57
System.out.println("5-->" + df.format(data));

// 6.模式中的"E"表示输出为指数,"E"之前的字符串是底数的格式,
// "E"之后的是字符串是指数的格式
style = "0.00E000";
df.applyPattern(style);
// 6-->1.23E003
System.out.println("6-->" + df.format(data));

// 7.模式中的"%"表示乘以100并显示为百分数,要放在最后。
style = "0.00%";
df.applyPattern(style);
// 7-->123456.79%
System.out.println("7-->" + df.format(data));

// 8.模式中的"\u2030"表示乘以1000并显示为千分数,要放在最后。
style = "0.00\u2030";
// 在构造函数中设置数字格式
DecimalFormat df1 = new DecimalFormat(style);
// df.applyPattern(style);
// 8-->1234567.89‰
System.out.println("8-->" + df1.format(data));
-

其他

-

科学计数法问题

-
BigDecimal b = new BigDecimal("0.0000001");
// 输出结果:1E-7
System.out.println(b.toString());
// 输出结果:0.0000001
System.out.println(b.toPlainString());
-
-

当 BigDecimal的值 小于一定值时(测试时发现:小于等于0.0000001)时,则会被记为科学计数法。可以使用 toPlainString()方法显示原来的值

-
-

去除无效的 0

-
BigDecimal b = new BigDecimal("0.000000100000000");
// 1E-7
System.out.println(b.stripTrailingZeros().toString());
// 0.0000001
System.out.println(b.stripTrailingZeros().toPlainString());
-
-

stripTrailingZeros() 方法的本质是去除掉多余的0,其返回数据类型是BigDecimal,同样的在使用时需要注意科学技术法的问题

-
-

保留小数位

-
double num = 13.154215;
// 方式一
DecimalFormat df1 = new DecimalFormat("0.00");
String str = df1.format(num);
// 13.15
System.out.println(str);
// 方式二
// #.00 表示两位小数 #.0000四位小数
DecimalFormat df2 =new DecimalFormat("#.00");
String str2 =df2.format(num);
// 13.15
System.out.println(str2);
// 方式三
// %.2f %. 表示 小数点前任意位数 2 表示两位小数 格式后的结果为f 表示浮点型
String result = String.format("%.2f", num);
// 13.15
System.out.println(result);
-

大小比较

-

在比较两个BigDecimal的值是否相等时,要特别注意,使用 equals() 方法不但要求两个BigDecimal的 值相等 ,还要求它们的 scale()相等,因此如果只是比较数值的大小,必须使用 compareTo() 方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于

-
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("123.45600");
// false,因为scale不同
System.out.println(d1.equals(d2));
// true,因为d2去除尾部0后scale变为2
System.out.println(d1.equals(d2.stripTrailingZeros()));
// 0
System.out.println(d1.compareTo(d2));
-

附录

-
    -
  • Java中浮点类型的精度问题 double float
  • -
  • 廖雪峰 BigInteger
  • -
  • 廖雪峰 BigDecimal
  • -
  • BigDecimal
  • -
  • Java 9中新的货币API
  • -
-]]>
- - Util - - - Util - -
- - Charles 使用教程 - /2018/11/29/charles/ -

-

Charles is an HTTP proxy / HTTP monitor / Reverse Proxy that enables a developer to view all of the HTTP and SSL / HTTPS traffic between their machine and the Internet. This includes requests, responses and the HTTP headers (which contain the cookies and caching information)

- -

Charles是一个HTTP代理/ HTTP监视器/ 反向代理,使开发人员能够查看其机器和Internet之间的所有HTTP和SSL / HTTPS流量,这包括请求,响应和HTTP标头(包含cookie和缓存信息)

-

Charles

-

主要特点

-
    -
  • SSL代理 - 以纯文本格式查看SSL请求和响应
  • -
  • Bandwidth Throttling模拟较慢的Internet连接,包括延迟
  • -
  • AJAX调试 - 以树或文本形式查看XML和JSON请求和响应
  • -
  • AMF - 以树形式查看Flash Remoting / Flex Remoting消息的内容
  • -
  • 重复请求以测试后端更改
  • -
  • 编辑测试不同输入的请求
  • -
  • 用于拦截和编辑请求或响应的断点
  • -
  • 使用W3C验证器验证记录的HTML,CSS和RSS / atom响应
  • -
-
-

本篇文章操作均基于Charlers 4.2.8版本,及 macOS 10.14.5 版本

-
-

安装

-
    -
  • Windows:略
  • -
  • macOS:略
  • -
-
-

下载地址:官方Charles

-
-

激活

-

有能力,请支持付费支持正版~
-有能力,请支持付费支持正版~
-有能力,请支持付费支持正版~

-

仅供个人学习研究和交流使用,请勿用于任何商业用途。
-Charles ——> Help ——> Register Charles…

-
Registered Name: https://zhile.io
License Key: 48891cf209c6d32bf4
-

激活密钥来自知了

-

配置

-

配置流程

-
    -
  1. 获取操作系统网络IP地址
  2. -
  3. 修改客户端网络IP连接
  4. -
  5. 在操作系统及客户端上安装证书
  6. -
  7. 设置SSL代理
  8. -
-
-

以上配置要求,操作系统(Windows,macOS)及客户端(Android,iOS)连接在 同一WiFi网络

-
-

获取系统 IP

-

不管是是 Windows 系统还是 macOS 系统都可以通过 Charles 来获取,获取方式 Help ——> Local IP Address
-charles-ip

-
Windows
-

使用命令行查看网络 IP 地址 ipconfig
-charles-windows-ip

-
macOS
-
    -
  • 使用命令行查看网络 IP 地址 ifconfig en0
    -charles-mac-ip
  • -
  • macOS系统设置查看IP 地址 System Preferences ——> Network
    -charles-network-ip
  • -
-

查看监听端口

-

Proxy——> Proxy settings...
-charles-view-port

-

客户端设置

-

要求手机网络与 PC 网络同链接在一个路由器网络下,这样手机的请求都将通过 PC,因此在 Charles 上可以看到手机上的网络请求。

-

手机上安装下面的步骤请看下面的详细介绍,安装完证书,Charles 将会收到提示,进行允许即可
-charles-allow

-
Android
-
    -
  • -

    修改网络配置选项

    -
  • -
  • -

    导入Charles证书,使用浏览器打开 www.charlesproxy.com/getsslhttp://chls.pro/ssl,下载证书,并进行安装

    -
  • -
-
iOS
-
    -
  • -

    修改网络配置选项

    -
  • -
  • -

    导入Charles证书

    -
  • -
  • -

    证书授权

    -
  • -
-
模拟器
-

Charles设置

-
    -
  • Install Charles Root Certificate
    -完成客户端的设置,我们此时再对 Charles 进行设置,首先我们先进性安装 Charles 证书,Help ——> SSL Proxying... ——> Install Charles Root Certificate
    -charles-install-system
  • -
  • 添加证书
    -charles-add
  • -
  • 证书授权设置
    -charles-ca-settings
  • -
  • SSL Proxy settings
    -charles-ssl-settings -
      -
    • Host:为需要过滤的域名地址,* 表示不过滤
    • -
    • Port:固定为443,* 表示任意端口
    • -
    -
  • -
-

抓包

-

不废话,请看图
-charles-overview

-
    -
  • Structure:视图将网络请求按访问的域名分类
  • -
  • Sequence:视图将网络请求按访问的时间排序
  • -
-
-

客户端请不要开启其他代理

-
-

进阶

-

过滤网络请求

-
    -
  • 方法一:在上面抓包的截图中,已经讲过,适用于 临时型 对请求进行过滤
  • -
  • 方法二:Proxy ——> Recording settings ——> include ,适用于 经常性 请求过滤
    -charles-filter-often
  • -
-

Map 功能

-

设置本地映射

-

指的是将网络请求重定向到本地的文件,适用于开发过程中,把线上的静态资源映射到本地,这样可以方便调试并及时查看效果,确定无误后再发布到线上环境
-charles-map-local

-

设置远程映射

-

指的是将网络请求重定向到另一个网络请求地址,适用于开发过程中,需要将请求重定向到其他的服务上
-charles-map-remote

-

重复发送网络请求

-

可以更加需要重复一次或多次的请求,对于多次的请求可用于服务器的压力测试
-charles-repeat

-

常见问题

-
    -
  1. Charles无法抓取到客户端网络请求 -
      -
    • 查看你的客户端网络设置,是否正确
    • -
    • 查看你的客户端和 Charles 是否是处于同一网络环境
    • -
    -
  2. -
  3. Charles 无法抓取 Https 网络请求 -
      -
    • 查看你的客户端和 Charles 是否安装证书,并设置终是允许
    • -
    -
  4. -
  5. 网络请求及网络响应信息中文乱码
  6. -
  7. 如果需要抓本地应用(即拦截电脑上的应用接口),请确保你未开启翻墙代理软件
  8. -
-

附录

-
    -
  • Charles Document
  • -
  • Charles抓包的安装,使用说明以及常见问题解决
  • -
  • 抓包工具Charles的使用心得
  • -
  • Charles抓包https
  • -
-]]>
- - DevTool - - - Charles - -
- - Google Cloud Platform for VPN - /2018/11/07/cloud-gcp/ - cloud-gcp

-

随着云产品的普及推广,各路国际大场也是纷纷推出了相关云产品的试用,其中具有代表性的Google CloudAmazon,本篇主要讲解Googel Cloud产品的试用,并搭建SSR服务

- -

Google Cloud 特点

-
    -
  • 可使用所有Cloud Platform产品
  • -
  • 免费获得$300赠金
  • -
  • 免费使用结束后不会自动收费
  • -
-

准备

-
    -
  • Google Email
  • -
  • visa 信用卡(需要$1进行认证,认传完成后返还$1)
  • -
-
-

因为Google本身在大陆是无法正常访问的,因此需要先自备梯子,可以先使用Lantern

-
-

GCP

-

申请Google Cloud Platform

-

官网申请https://cloud.google.com/free

-

gcp-register1

-
    -
  • 国家地区:中国
  • -
  • 服务条款:同意
  • -
  • 动态邮件:可选,根据自身需要勾选
  • -
-

gcp-register2

-

根据需要填写一些信息,由于我的Google账号已是开发者账号,一些信息都是完善的,所以Google直接关联了信息,因此也不会再扣除$1,如果你是新账号,详细步骤可参考附录

-

VM创建

-

在创建VM之前,我们先进行网络防火墙修改,避免后续的麻烦

-

gcp-firewall-settings
-gcp-create-firewall
-规则设置如下:
-gcp-firewall-rule

-
    -
  • 名称:自己命名一个用于区分其它得规则
  • -
  • 来源IP地址范围:0.0.0.0/0,这个不要写错
    -其它按照图上设置即可
  • -
-

创建VM实例

-

gcp-create-vm
-gcp-create-vm-init
-gcp-create-vm-course

-
    -
  • 名称:自己写一个即可
  • -
  • 地区:建议选亚洲,别人推荐asia-east1-c台湾彰化县实测延迟低,我这里选择了香港
  • -
  • 机器类型:选微型(1个共享vCPU)
  • -
  • 启动磁盘:推荐CentOS 7,当然也可以其它,选择自己熟悉的系统即可
    -其中关于网络的设置如下:
    -gcp-create-vm-network
  • -
  • 名称:任意输入即可(小写字母开头,不能为大写字母)
  • -
-

设置完成后,创建VM实例

-

连接VM

-

当然,你可以使用浏览器打开连接VM
-gcp-link-vm-chrome

-

经过实际操作,你会发现,在浏览器中操作延迟很高,因此我们就采用其它客户端去连接刚刚创建的这台服务器,下面分别以 Xshell(Windows)iTerm(macOS)来演示如何与 GCP 建立连接

-

使用Xshell

-

密钥生成

-
    -
  1. 新建用户密钥生成向导
    -gcp-link-vm-xshell1
  2. -
  3. 密钥类型长度设置
    -gcp-link-vm-xshell2
  4. -
  5. 生成密钥
    -gcp-link-vm-xshell3
  6. -
  7. 设置密钥名称及密码
    -gcp-link-vm-xshell4
  8. -
  9. 保存密钥
    -gcp-link-vm-xshell5
  10. -
-

GCP添加密钥

-
    -
  • 元数据
    -gcp-link-vm-settings
  • -
  • SSH
    -gcp-link-vm-ssh
  • -
  • SSH密钥添加
    -gcp-link-vm-create-ssh
  • -
-

Xshell 连接服务

-
    -
  • 配置连接的服务器地址
    -gcp-link-vm-ssh-ip
  • -
  • 配置连接服务器的密钥
    -gcp-link-vm-ssh-login
  • -
-

使用 iTerm

-
    -
  1. 添加公钥到 SSH 管理
  2. -
  3. 使用 SSH 命令进行连接
  4. -
-

gcp-item2-ssh

-
-

如果你本地没有已有秘钥,或者你需要生产一对 RSA 新秘钥,可参考MacBook Pro 疑难杂症文章的 免密登录服务器 的前半部分内容

-
-

准备工作

-

内核升级

-
# 切换到root用户
sudo -i
# 安装wget
yum install -y wget
# 安装bbr
wget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh
# 给bbr.sh文件设置权限
chmod +x bbr.sh
# 启动bbr.sh脚本
./bbr.sh
-

执行./bbr.sh安装过程如下
-gcp-centos-bbr

-

执行完成后,会提示,输入y回车后重启,这时需要等待几分钟

-

重启完成后,重新连接服务器

-
# 切换到root用户
sudo -i
# 查看内核(版本大于4.13或以上版本,就表示OK)
uname -r
-

选择安装服务

-

对于 SSR 和 v2ray 可以提供不可描述的服务,由于v2ray 功能更加强大,并且更加隐蔽,不易被发现,因此极力推荐使用 v2ray 方式

-

SSR

-

通过以上的配置,我们可以使用Xshell进行SSR工具的安装,安装SSR工具前,需要先升级系统内核,按照如下执行命令

-

安装SSR

-
# 切换到root用户
sudo -i
# wget设置
wget --no-check-certificate -O shadowsocks-all.sh
# 下载安装SSR
https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks-all.sh
chmod +x shadowsocks-all.sh
# 执行SSR运行脚本
./shadowsocks-all.sh 2>&1 | tee shadowsocks-all.log
-

安装过程步骤如下:

-
    -
  • 选择版本:推荐ShadowsocksR,输入2
  • -
  • 设置密码
  • -
  • 设置端口
  • -
  • 选择加密方式,这里选择chacha20,输入12
  • -
  • 选择协议,这里选择auth_sha1_v4,输入3
  • -
  • 选择混淆方式,这里选择http_simple,输入2
    -gcp-link-vm-ssh-install
  • -
-

等待安装完成,提示如下:
-gcp-link-vm-ssh-finish

-

根据安装完成后提示的信息配置你的SSR客户端即可

-

修改 SSR 配置

-

在实际过程中,我们安装完成后,可能根据实际环境,需要修改配置,那么我们该怎么去修改呢,直接看下面命令

-
# shadowsocks-r 默认路径是 /etc/shadowsocks-r
vim /etc/shadowsocks-r/config.json
# 安装实际需要,更改后保存配置,然后重启shadowsocks-r 服务
/etc/init.d/shadowsocks-r restart
# 记得更新你客户端相关的配置
-

卸载 SSR

-
# 切换到root用户
sudo -i
# 进入脚本目录(可省略)
cd /home/<user-name>/
# 使用 help 查看指令(可省略)
./shadowsocks-all.sh -help
# 执行卸载
./shadowsocks-all.sh uninstall
-
-

当你不知道该应用拥有什么命令时,多用 help 来获取相关的指令

-
-

SSR 常用命令

-
# 启动SSR
/etc/init.d/shadowsocks-r start
# 退出SSR
/etc/init.d/shadowsocks-r stop
# 重启SSR
/etc/init.d/shadowsocks-r restart
# SSR状态
/etc/init.d/shadowsocks-r status
# 卸载SSR,默认目录 /home/<user-name>/
./shadowsocks-all.sh uninstall
-

v2ray

-

时间校准

-

对于 v2ray,它的验证方式包含时间,就算是配置没有任何问题,如果时间不正确,也无法连接 v2ray 服务器的,服务器会认为你这是不合法的请求。所以系统时间一定要正确,只要保证时间误差在90秒之内就没问题

-
# 查看 VPS 时间
date -R
# +0000表示时区
Sat, 23 Nov 2019 17:21:50 +0000
# 将系统服务时间设置成本地时间
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 检查服务器系统时间是否和本地时间一样
date -R
-

安装 v2ray

-
# 安装脚本
bash <(curl -L -s https://install.direct/go.sh)
# 安装完成,v2ray 并不会自动运行,因此需要手动启动服务
sudo systemctl start v2ray
# 查看 v2ray 运行状态
service v2ray status
-

基本文件安装路径地址

-
    -
  • /usr/bin/v2ray/v2ray:V2Ray 程序;
  • -
  • /usr/bin/v2ray/v2ctl:V2Ray 工具;
  • -
  • /etc/v2ray/config.json:配置文件;
  • -
  • /usr/bin/v2ray/geoip.dat:IP 数据文件
  • -
  • /usr/bin/v2ray/geosite.dat:域名数据文件
  • -
-

安装过程截图如下
-gcp-v2ray-install

-

配置修改

-

客户端连接

-

问题排查

-

每到敏感时期,一大批服务都会被封,这次我的服务也不理外,这里就讲一讲我是如何排除问题。

-
-

所处环境说明:
-0. VPS 上安装的 SSR 服务

-
    -
  1. 可以使用 SSH 工具(比如:Xshell)可以连接 VPS 机器
  2. -
  3. 在 VPS 中 ping google.com 是可以的
  4. -
  5. 连接 VPS 的客户端,无法正常访问国外网站
  6. -
-
-

出现以上情况,大概率是当前的 SSR 服务端口被封了,可以通过以下方式来验证

-
    -
  1. -

    使用国内站长工具端口扫描检查下 VPS 的端口是否可以正常访问,地址:http://tool.chinaz.com/port

    -
      -
    • 如果提示关闭,说明国内无法访问该 VPS 对应的端口服务
    • -
    • 如果提示开启,说明访问正常
    • -
    -
  2. -
  3. -

    使用用国外端口扫描网站进行检查你的 VPS 服务是否可以正常访问,地址:https://www.yougetsignal.com/tools/open-ports

    -
      -
    • 如果检查结果为open,说明国外可以正常访问你的 VPS 对应端口的服务
    • -
    • 如果检查结果为close,说明国外无法访问
    • -
    -
  4. -
-

经过上面的两部,可以快速定位到问题,如果你检查出来的结果一样,则可以更改 VPS 上的 SSR 服务端口,重新启动 SSR 服务即可,在上面已经讲到了如何修改 SSR 配置,切记一起连加密方式
-协议混淆方式这些配置一并改掉,然后重启 SSR 服务,并修改连接 SSR 服务的客户端配置,其实这只是一个暂时的解决方法,我们可以使用更加隐蔽的 v2ray 服务

-

其它

-
    -
  • 查询余额
    -进入结算概览页面: https://console.cloud.google.com/billing/
  • -
  • 扣费计算
    -主机:$5/月.
    -流量:谷歌云服务器出口大陆流量1T以内价格约为0.23$/1G.
    -每个月可用流量:$300-$5*12=$240/12/0.23 ≈ 86G
  • -
  • SSR客户端
  • -
-

附录

-
    -
  • Google Cloud Platform免费申请&一键搭建SSR & BBR加速教程
  • -
  • Google Cloud使用VM虚拟机详细操作指南
  • -
  • ShadowsocksR客户端 各种隐藏使用技巧说明
  • -
  • v2ray社区:https://www.v2ray.com已被墙https://www.v2fly.org
  • -
  • 自建v2ray服务器教程
  • -
  • v2ray各平台图文使用教程
  • -
-]]>
- - Google - - - vpn - -
- - 该死的 Base64,我惹你了? - /2020/11/27/damn-base64/ - 在上一个项目中,由于客观原因,双方进行数据交换,用到对媒体文件(图片)进行了 Base64 编码处理,将编码后的数据存入了数据库,使用方再从数据库中取出数据进行解码恢复成图片,在实际处理中,这是最不推荐的做法。正确有效的做法是将资源文件存入到 OSS 系统中,数据库中记录文件的地址即可。但由于项目历史原因,无法使用 OSS 来处理,虽然说技术本质不难,编码存入,解码查看而已。但由于对方没有告知具体的编码方式,询问了好几次才最终给到对应的编码方式,浪费了大量的时间去沟通和试错,得不偿失

- -

对于 Base64 ,开发者或多或少都有听过,严格意义上讲 Base64 不是加密方式,它只是一种编码方式,本篇文章就来详细的聊一聊 Base64 这个熟悉又陌生的朋友

-

什么是 Base64

-

Base64 的原理

-

常见的 Base64

-

解决实际问题

-

参考

-
    -
  1. 密码学 | 庐山真面!你认为 Base64 是加密算法吗?
  2. -
  3. 什么是Base64?
  4. -
  5. Base64编码原理分析
  6. -
  7. Base64编码
  8. -
  9. Base64算法不一致可能会导致的坑
  10. -
-]]>
- - Java - - - Exp - -
- - 系统间数据同步 - /2020/07/21/data-sync/ - 对于不同系统之间的数据同步,业界通常有四种方式来进行数据交互,本篇文章就来聊一聊这四种数据交互方式

- -

共享文件

-
    -
  • 共享目录:可以是同台机器内的硬盘目录或者是挂载一个共享存储。在系统之间同步数据是非常简单,对共享存储中的文件读写就可以了
  • -
  • FTP 或者对象存储:不同系统间的批量文件往往用此方式实现,比如银行的批量回盘文件。数据量比较大的情况下大多采用此种方式,这种方式缺点就是不能实时,做个通知接口做到准实时性
  • -
  • zookeeper:zk 是基于文件的,可以同步简单数据,大数据量不太合适
  • -
-

接口

-

接口可以实时进行数据的交互

-
    -
  • HTTP 接口:这种接口是使用最多的,SpringCloud 所支持的,也是多个已购系统间采用的同步方式。使用方式,可以推送或拉取
  • -
  • RPC 方式:微服务的新起,诞生了很多 RPC 框架,也可以说是 RPC 框架促进了微服务的进步。比较流行的 RPC 框架有 Dubbo,gRPC,Thrift 等
  • -
-

消息队列

-

常用的 MQ 消息队列,在收集日志或者多个系统间准实时同步,优点是系统间解耦,削峰,易扩展等特点

-

数据库

-

同步结构化的数据可以采用数据库的形式,比如一个做批量的服务专门计算或统计业务数据,统计结果写入一个库,业务系统需要的时候直接从这个库读取即可

-

数据库同步的方式我理解大多数的单向少数几个系统间的同步。也可以多个系统间使用,比如用数据库的方式生成唯一 ID,多个系统共享使用

-]]>
- - Sync - - - Sync - -
- - 发布 jar 到 Maven中央仓库 - /2019/07/21/deploy-maven/ - 背景
-

和朋友一起维护的开源组织(我就是打个辅助,逃~),其中有一个系列的项目,这些项目统一通过 base 项目的 pom 文件管理这个系列项目依赖的第三方 jar,其他一些辅助项目(如:tools)项目主要是一些常用工具方法的封装,为了能让我们在不同机器,不同地点能够无缝切换,更重要的让使用的伙伴能以最简便的方式运行(避免不必要的配置),我们需要把通用的东西托管起来,那么就需要将这些配置依赖或辅助 jar 托管到 Maven中央仓库,话不多说,就跟着我的步骤来看看如何将 jar 发布到 Maven中央仓库

- -

准备

-
    -
  • Sonatype 账号
  • -
  • GPG
  • -
  • 需要发布的项目
  • -
-
-

这里以 Mac 系统演示

-
-

Sonatype

-

账号注册

-

Sonatype 账号注册地址:https://issues.sonatype.org/secure/Signup!default.jspa

-
-

记录好你的账号和密码,后续会用到

-
-

创建 issue

-

创建 issue 地址:https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&pid=10134

-

填写信息时,只填写必填项即可

-
    -
  1. Summary:简单的项目介绍,填写一下
  2. -
  3. Group Id:项目ID,用来定位应用 jar 的坐标,可参考官方说明
  4. -
  5. Project URL:项目主页地址
  6. -
  7. SCM url:Git仓库地址
  8. -
-
-

注意:

-
-
    -
  • Group Id -
      -
    • 无自己的域名:可以使用 Github,比如我的 GitHub 用户名是 BladeCode(也可以用你的组织名,如这里:twodragonlake),那么这里的Group Id应该填写 com.github.BladeCode,也可以使用 io.github.BladeCode
    • -
    • 有自己的域名:按照要求添加一条 TXT 的 DNS 解析,用来验证你的 Group Id
      -ossrh-domain
    • -
    -
  • -
  • 可参考:OSSRH-45597
  • -
-

验证 Group Id

-

根据你是否有自己的域名,有不同的方式来验证,上面的创建 issue 的注意中已经说明了,这里不啰嗦了,直接看下图
-ossrh-ticket

-

GPG

-
    -
  • Windows:Gpg4win
  • -
  • macOS:gpg
  • -
-

安装

-

macOS 为例

-
# 安装
brew install gpg
# 验证
gpg --version
-

生成秘钥对

-
gpg --gen-key
-

ossrh-gpg-key

-

这里用于生成秘钥的用户名和邮箱,可以和你的 Sonatype 账号不一样,记录密码,在部署时需要用到

-

上传秘钥

-
# 上传秘钥,最好带上端口号
gpg --keyserver hkp://pool.sks-keyservers.net:11371 --send-keys <密钥ID>
# 验证秘钥,最好带上端口号
gpg --keyserver hkp://pool.sks-keyservers.net:11371 --recv-keys <密钥ID>
-

ossrh-gpg-send

-

上传到其他服务器,命令同上,更换地址即可

-
    -
  • hkp://keyserver.ubuntu.com:11371
  • -
  • hkp://keys.gnupg.net:11371
  • -
-

如果你忘记了你刚刚生成的秘钥,可以使用下面的命令来查看本地生成的所有秘钥

-
gpg --list-keys
-

配置

-

maven 配置

-
    -
  • 查看路径
    -macOS 可以使用 IDEA 查看 maven 的路径,/usr/local/Cellar/maven/3.6.0/libexec/conf
    -ossrh-maven-local
  • -
  • 修改 settings.xml 文件
    -在 <servers> 标签内,添加如下配置
    <server>
    <id>oss</id>
    <username>你注册的Sonatype账号</username>
    <password>密码</password>
    </server>
    -
  • -
-

pom 配置

-

配置你需要上传项目的 pom 文件

-
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>

<name>tdl-base</name>
<description>TwoDragonLake base pom</description>
<url>https://github.com/TwoDragonLake/tdl-base</url>

<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
</license>
</licenses>

<developers>
<developer>
<!-- 这里配置 gpg 生成秘钥时的用户名和邮箱 -->
<name>Jerry xu</name>
<email>incoder.xu@gmail.com</email>
</developer>
</developers>
<!-- 更改成你的项目信息 -->
<scm>
<tag>master</tag>
<url>https://github.com/TwoDragonLake/tdl-base.git</url>
<connection>scm:git:https://github.com/TwoDragonLake/tdl-base.git</connection>
<developerConnection>scm:git:https://github.com/TwoDragonLake/tdl-base.git</developerConnection>
</scm>

<profiles>
<profile>
<!-- 这个id有用的,用于发布命令 mvn clean deploy -P release(这个参数) -Dmaven.test.skip=true -->
<id>release</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<!-- Source -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<!-- Skip javadoc error -->
<!-- <configuration>
<failOnError>false</failOnError>
<doclint>none</doclint>
</configuration> -->
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Gpg Signature -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- 这个也是必须要的 以下两个<id>代码块中的id要与 setting.xml中的id一致 -->
<distributionManagement>
<snapshotRepository>
<id>oss</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>oss</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
</profile>
</profiles>
-

编译并部署

-
mvn clean deploy -P release -Dmaven.test.skip=true
-

然后在命令行的弹出中输入使用 gpg 命令生成秘钥时输入的密码,如果在命令行中没有弹框提示,那么可以在终端中输入export GPG_TTY=$(tty)命令,再次执行部署命令,部署完成参考下图提示
-ossrh-deploy

-

发布

-

编译构建验签

-

部署成功后,使用Sonatype登录 https://oss.sonatype.org网站,进行发布,在Build Promotion中选择Staging Repositories,然后选择对应你的groud idRepository,进行 close,这里的Close其实就是进行自动构建,进行验证
-ossrh-close

-

参看是自动化运行过程否有错误,正确如下截图没有错误提示,如果有错误提示,就按照提示内容进行处理
-ossrh-build

-

发布

-

构建成功无错误,后就可以发布了,其实发布和部署是一样的操作,只不过部署是进行Close,而发布是Release操作,此时会提示你发布成功后会删除Staging RepositoriesRepository 记录
-ossrh-release

-

查看

-

打开你的https://issues.sonatype.org,登录并查看,你的 issues 下,提示你,已经发布成功,稍后可以在https://search.maven.org中搜索到
-ossrh-deploy-success

-

搜索结果,可以查看到我们发布的包
-ossrh-search

-

异常

-

IDEA 中 not found

-

项目中引入的 jar,IDEA 中提示无法找到包,可以打开 IDEA 的 Preferences 中进行同步
-ossrh-idea-update

-

本地编译错误

-
    -
  • 无提示框提示输入密码
    -ossrh-local-build-comfrim
    -解决方法:export GPG_TTY=$(tty) 命令,重新编译
  • -
  • 无法连接sonatype
    -ossrh-local-sonatype
    -解决方法:查看settings.xml文件中 <server>标签中配置的 id 是否与项目pom文件的<distributionManagement> 标签下的 id 是否一致
  • -
-

sonatype 构建错误

-

查看构建过程中错误提示,我这里是应为无法验证签名,因此我将提示中的服务器地址,全部都再发布gpg的秘钥,注意地址开头是 hkp
-ossrh-build-error

-

附录

-

官方

-
    -
  • OSSRH Guide
  • -
  • 发布要求、规范
  • -
  • PGP签名使用
  • -
  • 发布项目文档
  • -
-

其他

-
    -
  • 发布 Maven 构件到中央仓库
  • -
  • 发布构件到Maven中央仓库
  • -
  • 如何发布Jar包到Maven Central Repository
  • -
-]]> - - Maven - - - Deploy - - - - IDEA 之 SpringBoot 应用部署 - /2019/11/19/deploy-springboot/ - 服务端由原来 混合式(Java+JSP)的方式演进成专注于提供服务 API(前后端分离)的方式,开发的明确分工,使的各自开发人员在各自领域的垂直技能的加强,以满足业务的快速迭代,因此也就在这两个方式中,项目的构建方式也有了一定的变化,混合模式中常编译为 war 包,而在前后端分离模式中常编译为 jar 包,这两种文件格式虽然都是一种压缩文件的格式,但实质还是有一些区别,那首先让我们来了解这两种文件它们之间的区别

- -

jar 文件与 war 文件的区别

-
    -
  1. war 文件常应用在 混合式 的项目中,war 文件包含Java 相关的项目文件、部署文件,还包含一些前端页面等引用的相关资源文件;jar 文件常应用在 前后端分离 的项目中,jar 文件主要包含Java 相关的项目文件、部署文件
  2. -
  3. war 文件中不包含 Tomcat相关文件,必须运行在 Tomcat 容器中;jar 文件中内置了 Tomcat 文件,可直接运行
  4. -
  5. war 文件通常使用 SSM 架构;jar 文件通常使用 SpringBoot/SpringCloud 架构
  6. -
  7. 无论是 jar 还是 war 都能够使用嵌套容器,java -jar来独立运行
  8. -
  9. 只有 war 才能部署到外部容器中;SpringBoot支持多种模板引擎,但JSP 只能在 war 中使用
  10. -
-

准备

-
    -
  • System:macOS
  • -
  • Java:JDK 1.8+
  • -
  • 编辑器:IDEA
  • -
  • 包管理:maven/gradle
  • -
  • 服务器连接:iTerm2
  • -
  • 项目示例: -
      -
    • rc-cluster-springboot,这是一个 gradle 管理的 SpringBoot 项目
    • -
    • rc-ssm,这是一个 maven 管理的 SSM 项目
    • -
    -
  • -
-
-

Windows 连接服务器工具可使用 Xshell 等代替

-
-

编译

-

编译 war

-
<!-- pom.xml 文件中要指明构建文件的类型 -->
<packaging>war</packaging>
-
# clean依赖并编译成 package,也可以执行 mvn clean package -DskipTests
mvn clean package -Dmaven.test.skip=true
-

deploy-maven-war

-

编译 jar(SpringBoot)

-

maven

-
<!-- pom.xml 文件中要指明构建文件的类型 -->
<packaging>jar</packaging>
-
GUI 操作
-

deploy-maven-jar

-
命令行 操作
-

定位到需要构建的模块下,执行如下命令

-
# clean依赖并编译成 package,也可以执行 mvn clean package -DskipTests
mvn clean package -Dmaven.test.skip=true
-

maven-package-jar

-

gradle

-
GUI 操作
-

deploy-gradle

-
命令行 操作
-

在需要编译的项目路径下,这里编译子模块springboot-start,前提需要在系统的环境变量中配置好 gradle,macOS 可参考MacBook Pro 初始化文章

-
# linux or macOS
gradle build
# Windows
./gradle build
-

deploy-gradle-build

-

部署

-

部署步骤:

-
    -
  1. 备份服务器对应服务,并停止服务运行(如果是第一次部署该服务,可省略)
  2. -
  3. 上传应用 jar/war 文件
  4. -
  5. 启动上传应用
  6. -
-
# 拷贝本地文件到指定服务器的指定目录
# root:服务器用户名
# ip:服务器地址
# :/data/app:拷贝文件到服务器的/data/app 路径下
scp ~/Desktop/start-1.0-SNAPSHOT.jar root@ip:/data/app
-

部署 war

-
-

由于 war 包并没有内置 Tomcat 等运行的容器,因此需要你的服务器已经安装了 Tomcat 等服务

-
-

部署 jar

-

iTerm2 部署

-
# ssh 连接到服务器后,启动应用,如果有需要请先停止已在运行的程序
# 查看程序的进程
ps -ef|grep 'java -jar'
# UID PID PPID C STIME TTY TIME CMD
# root 6760 7103 0 19:59 pts/3 00:00:00 java -jar /data/app/start-1.0-SNAPSHOT.jar

# 终止程序进程
kill -9 pid

# nohup:后台运行程序,也就说当控制台终止,并不会停止启动的服务
# spring.log:当前目录下输出日志记录的文件名
nohup java -jar start-1.0-SNAPSHOT.jar > spring.log 2>&1 &

# 滚动查看日志输出
tail -f spring.log

# 如果不需要查看输入出的日志,运行如下脚本
nohup java -jar start-1.0-SNAPSHOT.jar &
-

tail 命令

-
    -
  • 命令格式:tail[必要参数][选择参数][文件]
  • -
  • 功能描述:用于显示指定文件末尾内容,不指定文件时,作为输入信息进行处理;查看日志文
  • -
  • 示例:tail -f spring.log
  • -
  • 命令参数 -
      -
    • -f 循环读取
    • -
    • -q 不显示处理信息
    • -
    • -v 显示详细的处理信息
    • -
    • -c<数目> 显示的字节数
    • -
    • -n<行数> 显示行数
    • -
    • –pid=PID 与-f合用,表示在进程ID,PID死掉之后结束.
    • -
    • -q, –quiet, –silent 从不输出给出文件名的首部
    • -
    • -s, –sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒
    • -
    -
  • -
-

Alibaba Cloud Toolkit

-

alibaba-cloud-toolkit

-

视频教程:https://cloud.video.taobao.com/play/u/650480598/p/1/e/6/t/1/216889058961.mp4

-

Jenkins

-

关于 Jenkins 的部署,会单独出一篇相关的部署介绍,以及 Jenkins 的相关知识介绍,请移步使用 Jenkins 项目部署

-

问题

-

gradle 编译失败

-

deploy-gradle-build-error

-

原因:这里的提示spring-boot-2.1.6.RELEASE.jar文件无法正常下载
-解决办法:切换你的网络,或者修改 gradle 仓库镜像地址

-

缓存文件未下载完全

-

-

原因:文件未下载完全,编译时使用了缓存文件
-解决办法:删除~/.gradle/caches/modules-2/files-2.1路径下无法编译通过的包,这里是spring-boot-gradle-plugin

-

附录

-
    -
  • SpringBoot入门系列(四)—Spring Boot 项目打包运行
  • -
-]]>
- - IDEA - - - Deploy - SpringBoot - -
- - Docker 之 SpringBoot 项目部署 - /2019/01/10/docker-init/ - - - Docker - - - Deploy - SpringBoot - - - - Fiddler 初体验 - /2018/10/25/fiddler/ - 在开发的路上,有时候面对一些应用,我们可能回去分析研究它的实现以及数据交互等,在没有官方没有公开的Api提供时,我们会用到一项实用的技术,抓包,所谓的抓包,指的是截取网络传输发送与接收的数据包。其中在Windows平台上使用比较广泛的要数Fiddler

-

本节主要讲解Fiddler的相关配置及简单使用

- -

资源

-
    -
  • Windows 10
  • -
  • Fiddler
  • -
-

配置

-

需要使手机连接WiFi和电脑WiFi是使用同一个网络

-

Fiddler 初始化

-

fiddler-config

-
-

默认端口:8888

-
-

网络地址

-

获取电脑所连接的网络IP地址
-fiddler-ip
-这里获取的IP地址,将用于手机连接网络的代理

-

手机配置

-

关于手机相关的配置操作,步骤已经通过下面的视频展现。

-
    -
  1. 连接与电脑相同的WiFi
  2. -
  3. 修改网络代理
  4. -
  5. 手动模式,并设置电脑端获取的IP地址及Fiddler默认端口号8888
  6. -
  7. 网络连接刷新
  8. -
  9. 获取并下载安装Fiddler证书
  10. -
-

其它

-

通过以上操作,现在可以在电脑端Fiddler工具中,拦截获取经过的所有网络信息。而我们一般是查看或者是分析某一款应用的数据信息,这样在查看起来就比较费力,那么我们就借助Fiddler提供的过滤功能
-fiddler-filter

-

选择过滤方式中

-
    -
  1. 第一项有三个选项,不做更改:
    -“No zone filter”;
    -“Show Only Intranet Hosts”;
    -“Show Only Internet Hosts”
  2. -
  3. 第二个选项是只监控以下网址,如只监控百度,在下面的输入框里填上 www.baidu.com
    -“No Host Filter”:不设置hosts过滤
    -“Hide The Following Hosts”:隐藏过滤到的域名
    -“Show Only The Following Hosts”:只显示过滤到的域名
    -“Flag The Following Hosts”:标记过滤到的域名
  4. -
  5. 文本框内输入需要过滤的域名,多个域名使用”;“分号分割。
  6. -
-
-

fiddler默认会检查http头中设置的host,强制显示http地址中域名。

-
-]]>
- - DevTool - - - Fiddler - -
- - Flowable(一)初识 - /2019/09/25/flowable1/ - Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据等,众所周知,Flowable是Activit的一个分叉,Flowable的第一个版本(5.22.0)是基于Activit(5.21.0),关于为什么Flowable会从Activit分叉,感兴趣可以查看Flowable官方的文章Flowable and Activiti: What the Fork?!,这里不在赘述这些内容

- -

Flowable官方文档介绍,可知Flowable遵循BPMNCMMNDMNFrom设计指导

-
    -
  • BPMN(Business Process Model and Notation):用于流程管理
  • -
  • CMMN(Case Management Model and Notation):用于案例管理
  • -
  • DMN(Decision Model and Notation):用于决策规则
  • -
  • Form:用于表单和任务表单管理
  • -
-

Flowable 直运行

-
-

这里所说的"直运行",是指不需要写任何代码,仅需要改动相关的配置就可以运行起Flowable应用程序

-
-

准备工作

-
    -
  • Flowable v6.4.2
  • -
  • MySQL8+
  • -
  • JDK & Tomcat 环境
  • -
-
-

MySQL8+ ,JDK,Tomcat环境代建可参考Linux 之 MySQLLinux 常用应用安装Windows 之 常用应用安装

-
-

已下操作均在Windows上,macOS上相差不大,操作流程基本一致

-

war部署

-
    -
  1. 解压flowable.zip文件
    -flowable-zip
  2. -
  3. 拷贝需要启动的war到安装的Tomcat的webapps路径下
    -flowable-tomact
  4. -
  5. 命令行中执行startup.bat命令,或执行Tomcat的bin路径下,启动startup.bat文件
    -flowable-startup
  6. -
  7. 第一次启动,Tomcat控制台应该会出错,因为flowable-admin.war数据库配置默认使用H2数据库,我们需要修改数据库配置连接等信息
    -flowable-mysql-config -
    -
      -
    • 文件地址:<your tomcat path>/webapps/flowable-admin/WEB-INF/classes路径,flowable-default.properties文件及application-dev.properties文件
    • -
    • MySQL中需要一个名为 flowable 的数据库,没有请创建一个CREATE DATABASE flowable
    • -
    • 由于我使用的是 MySQL8 ,Tomcat 中不包含此驱动 jar 包,因此需要手动下载mysql-connector-java-8.x.x(和你数据库匹配版本).zip文件进行解压,拷贝mysql-connector-java-8.x.x.jar文件到 <your tomcat path>/lib路径下
    • -
    -
    -
  8. -
  9. 重新在命令行中执行startup.bat命令,或执行Tomcat的bin路径下,启动startup.bat文件
  10. -
  11. 正常情况到此等待服务器启动完成,如果不能正常启动,请查看Tomcat控制台是否有错误,按照提示解决错误,直到Tomcat不再有错误提示即可
  12. -
-

使用

-
    -
  1. 访问http://localhost:8080/flowable-idm,默认账号:admin,默认密码:test
    -flowable-admin
  2. -
  3. 访问http://localhost:8080/flowable-admin,后台管理
  4. -
  5. 访问http://localhost:8080/flowable-modeler,流程定义管理
  6. -
  7. 访问http://localhost:8080/flowable-task,用户任务管理
  8. -
  9. 访问http://localhost:8080/flowable-rest/docs,流程引擎对外提供的API接口
  10. -
-

Flowable 集成运行

-
-

这里所说的"集成运行",是指通过Flowable官方提供的jar文件,集成到我们的项目中运行的方式

-
-

Flowable 使用

-

其他

-

如何切换中文

-

Flowable中已包含中文语言,会根据操作系统语言,自动显示对应语言

-

startup.bat异常

-

查看控制它异常,例如当前flowable启动默认端口8080,被占用
-flowable-aleady-bind

-

解决方法:查找占用端口进程netstat -ano|findstr 端口号,并kill它taskkill -PID 进程号 -F
-flowable-kill-task

-]]>
- - Flowable - - - Flowable - -
- - Flowable(二)Modeler-UI 集成 - /2019/10/24/flowable2/ - ]]> - - Flowable - - - Flowable - - - - Flowable(三)流程图绘制异常问题 - /2020/07/01/flowable3/ - -

附录

-
    -
  • HttpMediaTypeNotAcceptableException
  • -
  • HttpMediaTypeNotAcceptableException in Spring MVC
  • -
-]]>
- - Flowable - - - Flowable - -
- - Flowable(四)流程相关知识点 - /2020/07/21/flowable4/ - 流程部署 -

流程部署涉及到的表有 act_re_deploymentact_re_procdef,act_ge_bytearry,act_ge_property

-
    -
  • act_re_deployment(部署对象表):存放流程定义的显示名和部署时间,每部署一次增加一条记录
  • -
  • act_re_procdef(流程定义表):存放流程定义的属性信息,部署每个新的流程都会在这张表中添加一条记录,当流程定义的 key 相同时,使用的是版本升级
  • -
  • act_ge_bytearry(资源文件表):存放流程定义相关的部署信息,即流程定义文档的存放处。每部署一次会增加两条记录,一条是关于 bpmn 规则文件,一条是生成的流程图片(如果部署时只指定了 bpmn 一个文件,flowable 会在部署时解析 bpmn 文件内容自动生成流程图)。两个文件都是以二进制形式存储在数据库中
  • -
  • act_ge_property:主键生成策略表
  • -
- -

流程定义

-

流程定义

-

流程的一系列规则定义

-

流程实例

-

代表流程定义的执行实例,例如:员工 A 请假一天,他就必须发出一个请假流程实例申请

-

一个流程实例表示了所有的运行节点,我们可以利用这个对象来了解当前流程实例的进度等信息。流程实例就表示一个流程从开始到结束的最大流程分支,即一个流程中流程实例只有一个。流程实例通常也叫做执行实例的根节点,流程实例和流程定义为一对多的关系

-

执行实例(ProcessInstance Extends Execution),启动流程的时候会首先创建流程实例,然后创建执行实例;流程运转中永远执行的是自己对应的执行实例;当所有的执行实例按照规则执行完毕后,则实例随之结束;flowable 用这个对象去描述流程执行的每一个节点;流程按照流程定义的规则执行一次的过程,就可以表示执行对象 Execution。一个流程中,执行对象可以存着多个,但是实例只能有一个

-

节点

-

开始节点

-

开始节点代表一个规则的开始。在一个规则文件中,开始节点只能是一个,不能是多个。如果是多个则部署的时候会报错。子流程及引用流程也是如此。开始节点只能是一个。启动流程的时候,从开始节点让流程实例运行

-

结束节点

-

结束节点代表一个规则的结束。在一个规则文件中,结束节点可以是多个。如果实例运转到结束节点的时候,则表示当前的执行实例要结束,则流程也将随之结束

-]]>
- - Flowable - - - Flowable - -
- - Flutter(一)之环境搭建 - /2018/12/16/flutter-init/ - 这两年随着前端的高速发展,大前端的趋势下,Native移动应用开发市场在一定程度上被前端瓜分,加之硬件的快速迭代,性能已不存在明显的短板,React NativeVueAngular等等这些Web框架,对移动端也有了较大的提升,毕竟这样的开发效率会直线上升,并且大大减少了成本。技术的革新真的好快,如果不去学习,很快就会被淘汰

-

那就直接进入正题,flutter是一站式跨平台解决方案,一次开发,适配整个移动平台,并且是由Google进行主导开发,开源的一个项目,现如今已经迭代到1.0版本

-

本篇文章主要记录在macOS系统上搭建flutter开发环境的过程

- -

准备

-
    -
  • Android Studio开发环境(JDK,AndroidSDK,Gradle等等,这里不再赘述)
  • -
  • flutter SDK
  • -
  • Android Studio Plugin --> Flutter
  • -
-

步骤

-
    -
  1. -

    解压下载的flutter SDK,并配置环境变量,例如这里配置在.bash_profile文件中

    -
    # 打开 .bash_profile文件
    vim .bash_profile
    # .bash_profile文件中加入flutter sdk路径并保存
    export FLUTTER_HOME=/Users/blade/Documents/DevTools/flutter
    export PATH=$FLUTTER_HOME/bin:$PATH
    # 重新加载.bash_profile文件
    source .bash_profile
    -
  2. -
  3. -

    检查环境变量是否配置正确,如果有相关命令说明,表示已配置好环境变量

    -
    flutter -h
    -
  4. -
  5. -

    检查开发环境,第一次执行,应该提示如下图所示说明

    -
    flutter doctor
    -

    flutter-doctor
    -其实不难,看出我们需要安装一下其他辅助工具等

    -
  6. -
  7. -

    解决问题,按照如下命令,一步步执行,大概得1个小时左右(取决于你的网络情况)

    -
    # 允许协议(android-licenses
    flutter doctor --android-licenses
    # 安装libimobiledevice
    brew install --HEAD libimobiledevice
    # 安装ideviceinstaller
    brew install ideviceinstaller
    # 安装ios-deploy
    brew install ios-deploy
    # 安装cocoapods
    brew install cocoapods
    # cocoapods 初始化,这一步比较耗时,需要下载文件大致547M,需要耐心等待
    pod setup
    -
  8. -
  9. -

    以上步骤都正常运行后,再次检查环境,如下图所示结果,表示已完成flutter环境搭建

    -
    flutter doctor
    -

    flutter-finish

    -
  10. -
-

辅助

-

如果你不习惯或者不想使用Android Studio来开发Flutter,那么使用VS Code是最佳推荐的文本编辑器,只需要在VS Code中安装Flutter插件即可,它已包含所需的Dart语法插件

-

关于程序的运行,那么模拟器当然少不了,这里介绍下macOS上如何启动Android 模拟器

-
    -
  • 首先AndroidSDK的环境变量配置少不了
  • -
  • 配置emulator
    export ANDROID_HOME=/Users/blade/Library/Android/sdk
    export FLUTTER_HOME=/Users/blade/Documents/DevTools/flutter
    export PATH=$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$FLUTTER_HOME/bin:$PATH
    -
  • -
  • 启动
    # 查看已创建模拟器清单
    emulator -list-avds
    # 选择需要启动的模拟器,avd_name:表示从上面列表获取到的模拟器名称
    emulator -avd [avd_name]
    -
  • -
-

注意:

-
    -
  • 不推荐使用Genymotion,flutter的运行在此模拟器上有各种灵异bug
  • -
  • PANIC: Missing emulator engine program for ‘x86’ CPU.解决方式:创建一个x64的模拟器
  • -
-
-

问题

-

libusbmuxd version error during flutter install

-
brew update
brew uninstall --ignore-dependencies libimobiledevice
brew uninstall --ignore-dependencies usbmuxd
brew install --HEAD usbmuxd
brew unlink usbmuxd
brew link usbmuxd
brew install --HEAD libimobiledevice
-

Unbrewed header files were found in /usr/local/include

-

flutter-node

-

附录

-
    -
  • flutter docs
  • -
  • Flutter免费视频第一季-环境搭建
  • -
  • flutter安装记录过程
  • -
  • macOS上搭建Flutter开发环境
  • -
  • 官方命令行构建您的应用
  • -
-]]>
- - Android - - - flutter - -
- - 评论不自由,赞美无意义 - /2021/05/05/freedom-pact/ - 就像文章标题所述。每到三,五月这个时间,网络变得异常脆弱。各种“奇怪”的网站访问起来很费劲。对于一个技术人员,这些问题可以解决,但是每次都需要花费一定的时间和精力来应对这些,而且随着 GFW 的不断升级和加强,应对的策略和技术也是在不断迭代,前前后后已经出现了多种技术,本篇就以这个契机,梳理截止到 2021-05-05 所了解到关于 “代理” 相关的知识。现在主流的科学上网技术有 VPN、SS、SSR、V2Ray、Trojan、Trojan-Go,小众的 WireGuard、Brook、Snell 和 NaiveProxy 等

- -

本篇文章大量使用了 一灯不是和尚 作者发布 《科学上网工具哪个好? 》文章中的内容,在此感谢作者对各技术的汇总以及经验总结,本篇文章是在原文章的基础上进行的扩展补充

-
-

VPN

-

虚拟专用网络(Virtual Private Network,缩写:VPN)是常用于连接中,大型企业或团体间私人网络的通讯方法。它利用隧道协议(Tunneling Protocol)来达到发送端认证,消息保密与准确性等功能。

-

VPN 只是一个统称,它有多种具体实现。比如:

-
    -
  • PPTP(点对点隧道协议:Point to Point Tunneling Protocol);
  • -
  • L2TP(第二层隧道协议:Layer Two Tunneling Protocol);
  • -
  • IPsec(互联网安全协议:Internet Protocol Security);
  • -
  • WireGuard(一种协议);
  • -
  • OpenVPN(一种虚拟专用网络 Virtual Private Network(VPN)系统);
  • -
  • IKEv2(因特网密钥交换:Internet Key Exchange) 等
  • -
-

WireGuard

-

WireGuard 是由 Jason A. Donenfeld 开发,是最新的协议实现(在 2020 年,WireGuard 协议已被添加到 Linux 和 Android 内核中,从而为 VPN 提供商所采用。默认情况下,WireGuard 使用 Curve25519 进行秘钥交换,并使用 ChaCha20 进行加密,但还具有客户端和服务器之间预共享对称秘钥的功能)

-

OpenVPN

-

OpenVPN 是由 James Yonan 编写,实现了在路由或桥接配置和远程访问设施中创建安全点对点或站点对站点连接的技术。它实现了客户端和服务器应用程序。它不与IPsec兼容

-

OpenVPN 允许对等方使用预先共享的密钥、证书或用户名/密码相互验证。当在多客户端服务器配置中使用时,它允许服务器使用签名和证书颁发机构为每个客户端发布身份验证证书。

-

SS

-

SS 是 Shadowsocks 的缩写,中文名为影梭,为了避免关键词过滤,网友喜欢将 Shadowsocks 称为 “酸酸”,是一种基于 Socks5 代理方式的加密传输协议,也可以指定实现这个协议的各种开发包。Shadowsocks 是由 Clowwindy 为了自己使用谷歌查资料而编写;Shadowsocks 分为服务端和客户端,在使用之前,需要先将服务器端程序部署到服务器上面,然后通过客户端连接并创建本地代理。后来,他觉得这个东西非常好用,速度也很快,于是将源码提交到了 GitHub。由于其优秀的使用体验,Shadowsocks 被广泛传播,导致作者被某部门请去 “喝茶”。迫于压力 Clowwindy 于 2015-08-22 宣布停止维护此项目,并移除其个人页面所存储的源代码,而且保证永不再参与维护更新

-

虽然 Clowwindy 被迫放弃了 Shadowsocks,但开源界没有放弃,各路大神依旧在为 Shadowsocks 添砖加瓦,这就是开源的力量,倒下一个 Clowwindy,会有千千万万个 “Clowwindy” 站出来

-

SSR

-

SSR 是 ShadowsocksR 的缩写,网名爱称 “酸酸乳”,是在 Shadowsocks 的作者被请去喝茶之后,网名为 breakwa11 的用户发起的 Shadowsocks 的一个分支版本,它在 Shadowsocks 的基础上增加了一些数据混淆方式,修复了部分安全问题并提高了 QoS 优先级。由于 ShadowsocksR 在协议和混淆方面做了改进,更加不容易被 GFW 检测到,而且兼容原 Shadowsocks,并为新项目命名为 Shadowsocks-R,一开始部分代码由社区人员进行更新。由于不完全开源,也导致后来使用 SS 和 SSR 的用户分为两个阵营,互相撕逼,直到开发者 breakwa11 被人肉出来。breakwa11 最终决定删除 Shadowsocks-R 项目的所有代码,并解散了所有相关群组

-

事件始末澄清

-

ShadowsocksR 的作者一开始曾有过违反 GPL 协议,在发布二进制文件时不开放源码的争议。不过后来 Shadowsocks-R 项目由 breakwa11 采用了与 Shadowsocks 相同的 GPL、Apache、MIT 等多重自由软件许可协议

-
    -
  • 2017-07-19,ShadowsocksR 作者 breakwa11 在 Telegram 频道 ShadowsocksR news 里转发了深圳市启动 SS 协议检测的消息并被大量用户转发,在电报(TG)圈引发恐慌。
  • -
  • 2017-07-24,breakwa11 遭到自称 “ESU.TV” 的不明省份人士人身攻击,对方宣传如果不停止开发并阻止用户讨论此事件将发布更多包含个人隐私的资料,随后 breakwa11 表示遭到对方人肉搜索并公开个人资料。为防止对方继续伤害无关人士,breakwa11 删除了 GitHub 上的所有代码、解散相关交流群组,并停止 ShadowsocksR 项目
  • -
-

从本质上来说,Shadowsocks 与 ShadowsocksR 的基本原理相同,都是基于 Socks5 的代理工具,只在本地客户端和服务器对数据包加解密,然后使用 Socks5 协议转发加密的数据包,而不用在乎使用什么协议,所以 Socks5 代理比其他应用层代理速度要快的多

-

Socks5

-

这里顺带科普一下 Socks5,Socks5 代理的原理是把你的网络数据请求先发送到你的代理服务器,然后由代理服务器转发给目标;如果目标有反馈发送到代理服务器,那么代理服务器会将数据包直接传回到你的本地网络,整个过程只需要数据的二次传输,并没有额外的处理。

-
-

示例:现在呢在深圳,你的代理服务器在香港,如果你想要访问 Google,那么你首先需要把数据请求通过本地 Socks5 代理客户端发给在你在香港的服务器上的 Socks5 代理服务端,然后你在香港的服务器将数据请求发送给 Google,再把 Google 反馈的结果传到你香港的代理服务器,然后通过 Socks5 服务端回传到本地的 Socks5 客户端,这样就可以绕开 GFW 的检测而实现科学上网

-
-

显而易见,Socks5 代理的所有数据走的任然是公网,而且在公网传输过程中,没有对数据进行任何加密和混淆,这跟 VPN 在公网建立虚拟专用通道传输过程中,对数据高强度加密的方式完全不同。Shadowsocks 和 ShadowsocksR 只在客户端和服务器端对数据做了简单加密和认证,主要功能是流量转发,过强才是主要目的。虽然 ShadowsocksR 已经停止更新很久了,而 Shadowsocks 仍处于社区人员的更新和维护之中,不断修复漏洞并增加新功能,所以现在 Shadowsocks 比 ShadowsocksR 更强大

-

提醒

-

不要迷信 SSR 一定比 SS 强,也包括现在的 V2Ray,Trojan,甚至是 WireGuard 等,因为增加混淆意味着损失速度,混淆加密越是强悍,那么其速度和稳定性损失就越大,另外 SSR 至今已被研究透了,而且长时间没有更新维护,其流量特征是可以被 GFW 精准识别,所以用 SSR 和 SS 没有本质区别,由于 SS 一直更新维护,反而更稳定。

-

V2Ray

-

V2Ray 是在 Shadowsocks 被封杀后,为表示抗议而开发,属于后起之秀,功能更加强大,为抗 GFW 封锁而生。V2Ray 现在已经是 Project V 项目的核心工具。而 Project V 是一个平台,其中也包括支持 Shadowsocks 协议。由于 V2Ray 早于 Project V 项目,且名声更大,我们习惯称 Project V 项目为 V2Ray,所以我们平常所说的 V2Ray 其实就是 Project V 这个平台,也就是一个工具集。其中,只有 VMess 协议是 V2Ray 社区原创的专属加密通讯协议

-

V2Ray 目前支持一下协议(截止 2019-12)

-
    -
  • Blackhole: 中文名称“黑洞”,是一个出站数据协议,它会阻碍所有的数据的出站,配合路由(Routing)一起使用,可以访问被封杀的网站
  • -
  • DNS: 是一个出站协议,主要用于拦截和转发 DNS 查询。此出站协议只能接收 DNS 流量(包含基于 UDP 和 TCP 协议的查询),其它类型的流量会导致错误。在处理 DNS 查询时,此出站协议会将 IP 查询(即 A 和 AAAA)转发给内置的 DNS 服务器。其它类型的查询流量将被转发至原本的目标地址,DNS 出站协议在 V2Ray 4.15 中引入
  • -
  • Dokodemo-door: 中文名称“任意门”,是一个入站数据协议,它可以监听一个本地端口,并把所有进入此端口的数据发送到指定服务器的一个端口,从而达到端口映射的效果
  • -
  • Freedom: 是一个出站协议,可以用来向任意网络发起(正常的)TCP 或 UDP 数据
  • -
  • HTTP: 超文本传输协议,是传统的代理协议
  • -
  • Socks: 标准的 Socks 协议实现,兼容 Socks 4Socks 4aSocks 5『🙃目前无法正常访问』,也属于是一种传统的代理协议
  • -
  • VMess: 是 V2Ray 专用的加密传输协议,它分为入站和出站两部分,通常作为 V2Ray 客户端和服务器之间的桥梁。因为增加了混淆和加密,据说比 Shadowsocks 更安全。VMess 依赖于系统时间,请确保使用 V2Ray 的系统 UTC 时间误差在 90 秒之内,与时区无关。在 Linux 系统中可以安装 ntp 服务来自动同步系统时间
  • -
  • Shadowsocks: 包含入站,出站两部分协议,兼容大部分其它版本的实现。最早被个人开发的科学上网梯子协议,但 V2Ray 目前不支持 ShadowsocksR
  • -
  • Trojan: 特洛伊木马服务器如何对有效的特洛伊木马协议和其他协议(可能是 HTTPS 或任何其他探针)做出反应
  • -
  • VLESS: 是一个无状态的轻量传输协议,它分为入站和出站两部分,可以作为 V2Ray 客户端和服务器之间的桥梁,与 VMess 不同,VLESS 不依赖与系统时间,认证方式同样为 UUID,但不需要 alterId
  • -
  • Loopback: 是一个出站协议,可使出站连接被重新路由,最低支持版本 v4.36.0+
  • -
-

截止到 2021-05,V2Ray 可选的传输层配置有:TCP、mKCP,WebSocket、HTTP/2,DomainSocket、QUIC、gRPC。其中 mKCP、QUIC 和 TCP 用于优化网络质量;WebSocket 用于伪装;HTTP/2 和 DomainSocket 用于传输以及 TLS 加密

-

V2Ray 不仅可以在传输层配置 TLS 使用 HTTP 和 Socks 变成 HTTPS 和 Socks over TLS 协议,也可以使用 MTProto、Shadowsocks 和 VMess 通过传输层配置 TLS 加密伪装成 TLS 流量。所以,VMess 配置是 TLS 加密的最常见的做法,但没人会对 Shadowsocks 使用 TLS 加密,因为完全没有意义

-

客户端

-
-

V2Ray 客户端

-
-

Xray 与 XTLS

-

Xray 与 V2Ray 完全类同,Xray 是 Project X 项目的核心模块。因为 Xray 和 XTLS 黑科技 的作者 rpfx 曾是 V2fly 社区的重要成员,所以 Xray 直接 fork 全部 V2Ray 的功能,然后进行性能优化,并增加了新的功能,使 Xray 在功能上成为了 V2Ray 的超集,且完全兼容 V2Ray。

-

简而言之,Xray 是 V2Ray 的项目分支,Xray 是 V2Ray 的超集,就跟 Trojan-Go 和 Trojan-GFW 的关系类似,而且 Xray 性能更好,速度更快,更新迭代也更频繁。

-
-

由于 V2Ray-core 4.33.0 版本起,删除了 XTLS 黑科技,但任然支持 VLESS,所以是否原生支持 XTLS 是 Xray 和 V2Ray 最大的区别之一

-
-

Trojan 与 Trojan-Go

-

Trojan 原特指特洛伊木马,是一种计算机病毒程序。但是,我们今天所说的 Trojan 是一种新型的科学上网技术,全称为 Trojan-GFW,是目前最成功的科学上网伪装技术之一。你可以认为 Trojan 是 V2Ray 的 “WS + TLS” 模式的精简版,速度比 V2Ray 更快,伪装比 V2Ray 更逼真,更难以备 GFW 识别

-

Trojan 工作原理:Trojan 通过监听 443 端口,模仿互联网上最常见的 HTTPS 协议,把合法的 Trojan 数据伪装成正常的 HTTPS 通信,并真正地完整完成 TLS 握手,以诱骗 GFW 认为它就是 HTTPS,从而不被识别。Trojan 处理来自外界的 HTTPS 请求,如果是合法的,那么为该请求提供服务,否则将该流量交给 Caddy、Nginx 等 Web 服务器,由 Caddy、Nginx 等为其提供网页访问服务。基于整个交互过程,这样能让你的 VPS 更像一个正常的 Web 服务器,因为 Trojan 的所有行为均为 Caddy、Nginx 等 Web 服务器一致,并没有引入额外特征,从而达到难以识别的效果

-

Trojan-Go 是 Trojan-GFW 的分支项目,对 Trojan 进行性能优化,并增加不少新特性,Trojan-Go 性能和功能均有大幅度的提升,而且支持分流和 CDN

-

总结

-

通过上述,以及其他信息来源对这些技术有了一定的基本认识,从以下的几个方面来进行总结

-

原理不同

-

VPN:强调对公网传输过程中数据的加解密
-SS/SSR/V2Ray/Xray/Traojan:专注于在客户端和服务器间加密,公网传输过程中特征没有 VPN 明显

-

目的不同

-

VPN:是走在公网中自建的虚拟专用通道,使用强大的加解密算法,为数据传输安全性、私密性而生,被广泛应用于企业、高校、科研部门等远程数据传输领域
-SS/SSR/V2Ray/Xray/Trojan/Trojan-Go:是为了数据能够安全通过 GFW 而生,更强调的是对数据的混淆和伪装,加解密只是为了更好的隐藏数据特征而顺利通过 GFW 的检测

-

项目诞生的大致顺序

-

VPN > SS > SSR/V2Ray/WireGuard > Trojan/Trojan-Go > Xray

-

致每一个追求真理的人

-

凡事都有两面性,看如何去看待。其实我一直认为,有墙确实是一件好事,毕竟祖国互联网发展也不过 20 多年,在一定程度上过滤掉了一些没有自我认知,自我思考盲目跟风,觉得国外的月亮圆,国外什么都好的一群人;保障了在互联网开始萌芽的本土企业(毕竟国外的产品和技术都已经发展了好几轮,从解决问题的能力,使用的用户体验(不含本地化)等等方面都是完全甩开本地企业的服务)发展,提供给企业和网民一起成长的安全环境,在这一点上 GFW 功不可没。但作为一个技术从业人员来说,其中大部分的技术理念思想,技术平台等纯技术领域相关的东西来说不是那么友好,常常伴随着由于网络原因而造成的各种乱七八糟的问题,对于暂无本土相关替代服务支持时,在一定程度上阻碍了国内相关技术的发展进度和创新。

-

真如前面所述,凡事都有两面性。对于未知的事物,人类本能的会产生恐惧,因为它不可控,而互联网正真不可控的不是技术而是人,人是复杂的个体,一个技术的好坏不是它本身,而是使用的人在做什么样的事,而做的事在一定的环境下它是有好坏之分的。因此我在这里对自我约束,仅为获取相关学习的知识,不参与散布谣言、政治相关等言论,踏踏实实搞技术

-
-

希望未来的有一天,国内技术人员的输出是全球技术的风向标

-
-

参考

-
    -
  1. 试用 Always Free 云服务
  2. -
  3. 申请 Oracle Cloud 永久免费服务
  4. -
  5. 2021年申请永久免费甲骨文云 Oracle Cloud 并创建实例最全攻略
  6. -
  7. 多种科学上网指导教程汇总
  8. -
  9. V2ray XTLS黑科技
  10. -
-]]>
- - Agent - - - VPN - -
- - 专治各种网络不服 - /2020/02/27/fuck-gfw/ - 众所周知国内的开发已经由原来的“致敬”到现在软件生态领域的“引领”(目前来说还未到真正引领)世界技术发展,但是一个问题始终还是未能有所突破,作为中国的开发者每次在面对新的技术在环境搭建就劝退了一众人,很多开发所依赖的项目资源都来自国外服务,而由于中国的特殊,对很多国外服务的限制,让你在开始的第一阶段总是碰的鼻青脸肿,把大量的时间浪费在环境搭建的等待上,虽然现在很多开源组织或者一线大厂提供了相应的镜像服务方便国内的开发者,但很多都还需要我们自行去更改或者解决这些问题,本篇文章就是我的开发之路上的各种网络问题的解决办法

- -

Gradle

-

Gradle 作为新一代的包管理工具,早期作为 Android 项目的御用包管理,渐渐的越来越多的服务端项目也开始在使用 Gradle 来进行管理,至于 Gradle 和 Maven 的对比,我这里不做评论,请移步 Gradle 官方网站对两个包管理的比较 Gradle vs Maven Comparison

-
-

Gradle 镜像地址:https://services.gradle.org/distributions
-Gradle 腾讯镜像:https://mirrors.cloud.tencent.com/gradle

-
-

单项目配置

-

Android 项目

-

在 Android 项目中主要有下面这些配置文件

-
    -
  • build.gradle -
      -
    • 项目级别:在项目根目录,定义项目中所有模块共用的 Gradle 代码库和依赖项
    • -
    • 模块级别:在模块根目录,用于为其所在的特定模块配置构建设置,可以通过配置这些构建设置提供自定义打包选项(如额外的构建类型和产品变种),以及替换 main/ 应用清单或顶层 build.gradle 文件中的设置
    • -
    -
  • -
  • settings.gradle:项目的根目录下,用于指示 Gradle 在构建应用时应将哪些模块包含在内
  • -
  • xxxxx.gradle:模块配置文件,可将冗长的配置信息分块进行配置,比如依赖的版本统一管理等
  • -
-
-

更详细的介绍请查看 Android 官方说明 配置构建

-
-

项目的依赖仓库镜像配置,在项目级别的 build.gradle 文件中,如下进行镜像的指定示例

-
buildscript {
repositories {
// 添加或修改这里指向的镜像仓库地址,默认使用,https://maven.google.com/
google()
// 如果需要指定 google 的镜像地址,可注释掉上面的 google()默认配置,使用下面的显示指定配置
// google{
// url 'https://maven.aliyun.com/repository/google'
// }
jcenter()
maven {
// 比如修改这里的镜像指向地址
// url 'https://jitpack.io'
url 'https://maven.aliyun.com/repository/public'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
// 添加或修改这里指向的镜像仓库地址,默认使用,https://maven.google.com/
google()
jcenter()
maven {
// 比如修改这里的镜像指向地址
// url 'https://jitpack.io'
url 'https://maven.aliyun.com/repository/public'
}
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}
-
-

我们所依赖的 jar 文件,阿里云镜像中不一定都有,所以我们需要根据具体的项目实际情况调整

-
-

SpringBoot 项目

-

在 SpringBoot 项目中主要有下面这些配置文件

-
    -
  • build.gradle:主要配置文件,关于项目的依赖关系主要在该文件中配置
  • -
  • settings.gradle:项目信息文件,项目的一些可在这里配置,比如项目名称、子项目信息
  • -
  • xxxxx.gradle:模块配置文件,可将冗长的配置信息分块进行配置,比如依赖的版本统一管理等
  • -
-

如下,build.gradle 配置信息

-
plugins {
id 'org.springframework.boot' version '2.1.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}

group = 'org.incoder'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
sourceCompatibility = 1.8

repositories {
// 显式指定仓库访问地址,以下两个地址推荐阿里云镜像,按顺序执行
maven {
// 指向 阿里云 镜像仓库
url 'https://maven.aliyun.com/repository/public'
}
// maven {
// // 指向 spring 官方仓库
// url 'http://repo.spring.io/release'
// }
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}

test {
useJUnitPlatform()
}
-

对于 SpringBoot 项目,不管是全局镜像配置还是单项目的镜像配置,可能存在 SpringBoot 依赖的插件依赖无法下载,通常表现为卡在 Gradle: Download org.springframework.boot.gradle.plugin-xxxx.pom 这里,那则需要也配置插件镜像,在 settings.gradle 文件最上面加入如下配置

-
pluginManagement {
repositories {
maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
}
}
-

全局配置

-

在当前系统 ${USER_HOME}/.gradle/ 目录下创建 init.gradle 文件,将 Maven 和 Jcenter 仓库都指向阿里云镜像仓库

-
`// 如果你的 springboot plugin 下载也很慢,也可以全局设置插件下载地址`
pluginManagement {
repositories {
maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
}
}
// 项目依赖第三方包下载地址替换
allprojects{
repositories {

def ALIYUN_CENTRAL_URL = 'https://maven.aliyun.com/repository/central/'
def ALIYUN_JCENTER_PUBLIC_URL = 'https://maven.aliyun.com/repository/public/'
def ALIYUN_GOOGLE_URL = 'https://maven.aliyun.com/repository/google/'
def ALIYUN_GRADLE_PLUGIN_URL = 'https://maven.aliyun.com/repository/gradle-plugin/'
def ALIYUN_SPRING_URL = 'https://maven.aliyun.com/repository/spring/'
def ALIYUN_SPRING_PLUGIN_URL = 'https://maven.aliyun.com/repository/spring-plugin/'
def ALIYUN_GRAILS_CORE_URL = 'https://maven.aliyun.com/repository/grails-core/'
def ALIYUN_APACHE_SNAPSHOTS_URL = 'https://maven.aliyun.com/repository/apache-snapshots/'

all { ArtifactRepository repo ->
if(repo instanceof MavenArtifactRepository){
def url = repo.url.toString()
// central
if (url.startsWith('https://repo1.maven.org/maven2/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_CENTRAL_URL."
remove repo
}
// jcenter
if (url.startsWith('https://jcenter.bintray.com/') || url.startsWith('http://jcenter.bintray.com/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_JCENTER_PUBLIC_URL."
remove repo
}
// google
if (url.startsWith('https://maven.google.com/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_GOOGLE_URL."
remove repo
}
// gradle-plugin
if (url.startsWith('https://plugins.gradle.org/m2/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_GRADLE_PLUGIN_URL."
remove repo
}
// spring
if (url.startsWith('https://repo.spring.io/libs-milestone/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_SPRING_URL."
remove repo
}
// spring-plugin
if (url.startsWith('http://repo.spring.io/plugins-release/') || url.startsWith('https://repo.spring.io/plugins-release/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_SPRING_PLUGIN_URL."
remove repo
}
// grails-core
if (url.startsWith('https://repo.grails.org/grails/core/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_GRAILS_CORE_URL."
remove repo
}
// apache snapshots
if (url.startsWith('https://repository.apache.org/snapshots/')) {
project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_APACHE_SNAPSHOTS_URL."
remove repo
}
}
}

maven { url ALIYUN_CENTRAL_URL }
maven { url ALIYUN_JCENTER_PUBLIC_URL }
maven { url ALIYUN_GOOGLE_URL }
maven { url ALIYUN_GRADLE_PLUGIN_URL }
maven { url ALIYUN_SPRING_URL }
maven { url ALIYUN_SPRING_PLUGIN_URL }
maven { url ALIYUN_GRAILS_CORE_URL }
maven { url ALIYUN_APACHE_SNAPSHOTS_URL }
}
}
-

Maven

-

单项目配置

-

项目配置文件pom.xml文件中配置镜像地址,标签下添加标签配置阿里云镜像

-
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
-

全局配置

-

Maven 默认配置文件地址,Users/<PC_USER_NAME>/.m2目录下,如果没有,则新建一个settings.xml文件,进行镜像的配置

-
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">

<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云central仓库</name>
<url>https://maven.aliyun.com/repository/central/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云public仓库</name>
<url>https://maven.aliyun.com/repository/public/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云Google仓库</name>
<url>https://maven.aliyun.com/repository/google/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云gradle-plugin仓库</name>
<url>https://maven.aliyun.com/repository/gradle-plugin/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云Spring仓库</name>
<url>https://maven.aliyun.com/repository/spring</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云spring-plugin仓库</name>
<url>https://maven.aliyun.com/repository/spring-plugin/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云grails-core插件仓库</name>
<url>https://maven.aliyun.com/repository/grails-core/</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云Apache仓库</name>
<url>https://maven.aliyun.com/repository/apache-snapshots/</url>
</mirror>
</mirrors>

<proxies/>
<profiles/>
<activeProfiles/>
</settings>
-
-

具体的配置教程,可参考阿里云云效 maven

-
-

Homebrew

-

Homebrew是 macOS 系统的一款开源的包管理器

-

修改镜像

-
# macOS 系统修改 homebrew 镜像地址为清华镜像
git -C "$(brew --repo)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git
git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git
git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git
# 『可选』 如果之前有安装过 font 相关,则更改 font 相关的镜像地址,否则会提示 cask-fonts 不存在
git -C "$(brew --repo homebrew/cask-fonts)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-fonts.git
# 『可选』 如果之前有安装过 drivers 相关,则更改 drivers 相关的镜像地址,否则会提示 cask-drivers 不存在
git -C "$(brew --repo homebrew/cask-drivers)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-drivers.git
# 更换后测试工作是否正常
brew update
# 替换Homebrew Bottles源(以下方式二选一即可)
# 1. bash用户
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles' >> ~/.bash_profile
source ~/.bash_profile
# 2. zsh用户
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles' >> ~/.zshrc
source ~/.zshrc
-

恢复镜像

-
# macOS 系统恢复 homebrew 原镜像地址
git -C "$(brew --repo)" remote set-url origin https://github.com/Homebrew/brew.git
git -C "$(brew --repo homebrew/core)" remote set-url origin https://github.com/Homebrew/homebrew-core.git
git -C "$(brew --repo homebrew/cask)" remote set-url origin https://github.com/Homebrew/homebrew-cask.git
# 『可选』 如果之前有安装过 font 相关,则更改 font 相关的镜像地址,否则会提示 cask-fonts 不存在
git -C "$(brew --repo homebrew/cask-fonts)" remote set-url origin https://github.com/Homebrew/homebrew-cask-fonts.git
# 『可选』 如果之前有安装过 drivers 相关,则更改 drivers 相关的镜像地址,否则会提示 cask-drivers 不存在
git -C "$(brew --repo homebrew/cask-drivers)" remote set-url origin https://github.com/Homebrew/homebrew-cask-drivers.git
# 更换后测试工作是否正常
brew update
# 取消 Homebrew Bottles源设置(以下方式二选一)
# 1. bash用户,取消 HOMEBREW_BOTTLE_DOMAIN 设置
vim ~/.bash_profile
source ~/.bash_profile
# 2. zsh用户,取消 HOMEBREW_BOTTLE_DOMAIN 设置
vim ~/.zshrc
source ~/.zshrc
-
-

其他镜像:中科大

-
-

Github

-

资源文件无法加载

-

GitHub 上各种图片都无法加载,不仅仅是头像,包括各个仓库中的图片也是。开始以为是科学上网的问题,又或者是图片本身失效,可是验证下来都是无关的,经过一番折腾查找,原来是 DNS 被污染导致

-

解决方法:通过查看头像等文件的访问地址,了解到这些地址的域名都是 githubusercontent.com,然后通过IP 地址查询可以找到其对应的 IP 地址,并将其相关二级域名一起配置到 Hosts 文件中

-

host 路径

-
    -
  • macOS:/etc/
  • -
  • Windows:C:\Windows\System32\drivers\etc
  • -
-
# Github start
140.82.114.3 github.com
140.82.112.4 gist.github.com

185.199.108.153 assets-cdn.github.com
185.199.109.153 assets-cdn.github.com
185.199.110.153 assets-cdn.github.com
185.199.111.153 assets-cdn.github.com
# *.githubusercontent.com raw|gist|cloud|camo|avatars0-9|avatars
199.232.96.133 raw.githubusercontent.com
199.232.96.133 gist.githubusercontent.com
199.232.96.133 cloud.githubusercontent.com
199.232.96.133 camo.githubusercontent.com
199.232.96.133 avatars0.githubusercontent.com
199.232.96.133 avatars1.githubusercontent.com
199.232.96.133 avatars2.githubusercontent.com
199.232.96.133 avatars3.githubusercontent.com
199.232.96.133 avatars4.githubusercontent.com
199.232.96.133 avatars5.githubusercontent.com
199.232.96.133 avatars6.githubusercontent.com
199.232.96.133 avatars7.githubusercontent.com
199.232.96.133 avatars8.githubusercontent.com
199.232.96.133 avatars.githubusercontent.com
-

git clone 慢的想砸电脑

-

方式一:设置代理

-

这个无解,只能在开启代理的前提下,也给终端设置代理

-
# 只对 github.com,这里的 port 端口,需要你本地HTTP代理的端口来设置
git config --global http.https://github.com.proxy socks5://127.0.0.1:port
# 取消代理
git config --global --unset http.https://github.com.proxy
-

方式二:Gitee 中转

-

另一种方式,适用于你需要获取 GitHub 源码做相关的其他操作时,可以借助于 Gitee 来作为中转

-

improt-github-to-gitee

-

可以参考大佬的手把手教你

- -

方式三:cnpmjs镜像中转

-

在你需要 clone 的仓库地址中,添加 .cnpmjs.orggithub.com 的后面

-

github-cnpmjs

-

方法四:jsdelivr 免费 CDN 加速

-

适用于下载单个文件

- -

yum

-

yum 源配置文件路径:/etc/yum.repo s.d/

-
# 查看配置,Centos-xx.repo 类型的文件即为源
cd /etc/yum.repos.d && ls
# 备份源,创建 centos.back 文件夹,并将 *.repo 类型文件剪切到 centos.back 文件夹
mkdir centos.back && mv *.repo centos.back
# 下载安装国内源文件,分别下载了阿里和 163 的源文件
wget http://mirrors.aliyun.com/repo/Centos-7.repo
wget http://mirrors.163.com/.help/CentOS7-Base-163.repo
# 清空缓存
yum clean all
# 生成缓存
yum makecache
-

docker

-

docker 镜像源默认是使用的 docker hub(https://hub.docker.com) 的源,为加快效率,我们通常也将镜像源切换到国内镜像

-
# 修改 docker 源配置文件
vim /etc/docker/daemon.json
# 内容如下,分别是 docker 中国官方镜像,163,中国科学技术大学
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"https://hub-mirror.c.163.com",
"https://ustc-edu-cn.mirror.aliyuncs.com"
]
}
# 保存 daemon.json 文件修改,并退出
:wq
# 重启 daemon 服务
sudo systemctl daemon-reload
# 重启 docker 服务
sudo systemctl restart docker
-

参考

-
    -
  • Homebrew/Linuxbrew 镜像使用帮助
  • -
  • 阿里云公共代理库配置指南
  • -
-]]>
- - DevTool - - - Exp - -
- - Google Developer Days 2019 - /2019/09/12/gdd-2019/ -

-

连续三年申请参加 Google Developer Days,今年终于中签了,而且和好友大蛇丸及公司同事同时中签(可能是我们都使用了忍术)。嗯,终于离404公司又进了一步,哈哈哈~
-废话不啰嗦了,这篇文章就唠唠参加 GDD 的前前后后。

- -

众所周知每年 Google 会在 5 月份上旬在美国举行 Google 1/O (全球开发者大会)大会,在大会上无例外的推出新版本的 Android 系统(虽然只是 bate版本,今年 Android 取消了过去使用甜品命名的方式,而直接采用阿拉伯数字命名)等等软硬件上的技术探索和研究成果,给 Android 领域确立风向标。

-

而在中国,大概每年 9 月份会在中国上海举办 Google Developer Days,今年是第 4 年,可见 Google 对中国市场的重视。

-
    -
  • 2019
  • -
  • 2018
  • -
  • 2017
  • -
  • 2016
  • -
-

申请

-

由于 GDD 是不收取门票的,因此会对申请用户进行筛选,这个就要看运气了,可以通过以下的渠道获取信息,进行申请

-
    -
  • 微信公众号
    -
  • -
  • 微博 Google开发者
  • -
  • 知乎 谷歌开发者
  • -
  • 社区 GDG
  • -
  • 其他渠道
  • -
-

申请时需要填写一些资料,如实填写即可,剩下就是静待消息,如果审核通过,会发送邮件/短信通知你,不同的同学接收到的时间可能不同,具体的截止时间,以官方通知为准,没有通过的可查看官方合作的直播平台进行直播观看

-
-

众所周知,参加大会的基本是清一色的男同学,因此今年 Google 还专门有为女同学们提供了 1000 名的直通车,具体请移步官方公众号

-
-

参加

-

筛选通过后,那就是自己安排好自己的工作或者是学习,因为大会时间不一定是周末,以及安排好你的行程和住宿(两天的午餐都是由 GDD 提供)。我在杭州,因此就搭乘动车当天早上抵达上海虹桥,换乘地铁抵达目的地(上海世博中心)。由于支付宝并不支持上海地铁,因此需要提前下载一款 "Metro 大都会"应用

-

感受

-

满满当当两天下来,收货不少,这一届可以通过官方日程看出,重点是 Flutter 以及 TensorFlow 相关,大部分内容都是偏大前端这个领域,不管是相关应用场景的尝试还是一些技术细节和技术的巧妙实现,都能看得出 Google 在技术领域的话语权,其中有两个技术探索以及一场《挖掘事业发展潜力 - 开拓自己的道路》课堂,各位老师对职业发展讲解让我印象深刻

-
    -
  • 与 AR 相结合的 AR 导航(与滴滴合作),解决室内定位问题
  • -
  • 与艺术(音乐)结合,让技术有了温度,通过深度学习
  • -
  • 开拓自己的道路 -
      -
    • 对自己的专业技能需要达到融会贯通
    • -
    • 要主动的心态去工作,有企业家的精神
    • -
    • enjoy 的方式去对待自己所做的决定
    • -
    • 只有自己了解自己,才能将自己的推向更高的舞台
    • -
    -
  • -
-

另外通过现场感受,可以看到活动的现场屏幕边框元素是Material Desing中的,三角,圆,矩形,线条,和现场灯光融为一体,每一个视频动画都看得出他们在背后的付出,每一段音乐都那么的契合场景,这是我参加众多线下交流会,在现场感受最深的一次

-

其他

-

我们来看一看来自官方的活动精彩瞬间

- -

如何提高中签率

-

在知识星球中,看到有人分享

-

“简单说下对筛选的看法吧,报名的问卷非常的简单,都是一些有没有使用谷歌服务的选项。作为主办方,怎么样才能快速高效的在这之中找到自己想要的人呢?

-

这其实就是如何帮助谷歌建立你的用户画像,如果谷歌能找到更多的有利的信息,那么成功报名的机率自然会高。

-

那如何做到这点呢?其实很简单,提供使用谷歌服务频率最高最深的邮箱,因为谷歌可以很方便的获取到想要的信息!”

-

如何回顾

-

错过了现场参与,和视频直播,还能不能观看,答案是当然可以,官方会对直播视频进行剪辑,发布到bilibili视频网站,你可以关注Google中国 官方账号方便你第一时间活动更新动态,截止目前为止已发布,随后发布的我会及时更新

-
    -
  • -

    谷歌开发者大会开幕主旨演讲

    -
  • -
  • -

    移动端

    -
      -
    • Android 开发最新技术概览
    • -
    • Android 10 和隐私保护:使您的应用顺应变更
    • -
    • Android 无障碍:服务所有人
    • -
    • 利用 Kotlin 进行 Android 开发
    • -
    • 如何组装你的 Jetpack
    • -
    • CameraX:面向开发者的摄像头支持库
    • -
    • 移动Web技术拓展无限商机
    • -
    • AdMob 广告政策和工具
    • -
    • 用谷歌的新数据技术挖掘 App 变现潜力
    • -
    • ConstraintLayout + MotionLayout:打造丰富界面并为其制作动画效果
    • -
    • Material Theming:利用 Material 组件以极具表现力的方式构建主题背景
    • -
    • 利用 Material Design 设计深色主题背景
    • -
    -
  • -
  • -

    机器学习

    -
      -
    • 机器学习简介
    • -
    • 机器学习赋能智慧营销,成就商业新增长
    • -
    • 利用基准化分析和剖析功能提升应用性能
    • -
    -
  • -
-]]>
- - Google - - - GDD - -
- - Git 多账号 - /2018/10/06/git-account/ - 以前,git的账号只用来在Github上操作,随着积累Git管理的项目不仅仅只来自Github,还有一些其它Git项目托管的平台,例如:BitbucketCodingGiteeGitlib,以及公司内Git仓库

-

不同的托管平台有着不同的Git账号,无法用一个账号来管理其它的仓库,而且由于不同的托管平台账号不同,因此需要添加不同账号的公钥,这样我们再能在对应平台用对应的账号进行操作

- -

环境

-
    -
  • Windows 10 x64
  • -
  • Git version 2.16.0
  • -
-
-

这里Git的安装不在赘述

-
-

生成对应账号的密钥

-
# 进入到`your_pc_name/.ssh`,
cd .ssh
# Jerry.x@outlook.com 是我的Github的邮箱,这里需要替换成自己的邮箱
ssh-keygen -t rsa -C "Jerry.x@outlook.com"
# 命名文件名称或指定文件存放路径等
# 其它可以回车键进行确认,进行下一步
-

git-account

-
-

完成后,将会生成 id_rsa_company.pub(存放公钥)与 id_rsa_company(存放私钥)两个文件

-
-

添加公钥到托管平台

-
    -
  • .ssh 路径下,用文本编辑器打开 id_rsa_company.pub 文件,复制内容
  • -
  • 在托管平台上添加 ssh public key
  • -
-

以下以 GitHub 添加为例,其它平台类似
-git-add-key

-

添加配置文件

-

.ssh 路径下,创建 config 文件,无文件后缀名,如下示例

-
# 配置github.com
Host github.com
HostName github.com
IdentityFile C:\\Users\\Jerry\\.ssh\\id_rsa
PreferredAuthentications publickey
User BladeCode

# 配置 company.domain.com
Host company.domain.com
HostName company.domain.com
IdentityFile C:\\Users\\Jerry\\.ssh\\id_rsa_company
PreferredAuthentications publickey
User Jerry xu
-
    -
  • Host:的名字可以取为自己喜欢的名字
  • -
  • HostName:这个是真实的域名地址
    -例如:https://github.com/BladeCode/BladeCode.github.io.git,红色标注字段
  • -
  • IdentityFile:这里是id_rsa的地址
  • -
  • PreferredAuthentications:配置登录时用什么权限认证
    -可设为publickey,password publickey,keyboard-interactive 等
  • -
  • User:配置使用用户名
  • -
-

测试

-
ssh -T git@github.com
-

git-test

-
-

git@github.comgithub.com 就是上一步中 config 文件中配置的 HostName 字段内容

-
-]]>
- - Git - - - git account - -
- - Git 常用命令 - /2018/10/07/git-bash/ -

- -

记录 Git 日常操作常用命令

-

git config

-

Git级别:system(系统所有用户) < global(当前用户) < local(当前仓库)

-
    -
  • -

    查看配置信息

    -
    # 查看对应 Git 级别(--local;--global;--system)的配置信息
    git config --list --local
    -
  • -
  • -

    新增或修改

    -
    git config --global user.name xxxxx
    git config --global user.email xxx@xxxx.com
    -
  • -
  • -

    删除用户配置信息

    -
    # 如果当前只有一个用户,就不用加入xxxx
    git config --global --unset user.name xxxx
    -
  • -
-

git init

-
    -
  1. 把已有项目代码纳入 Git 管理
    # 进入项目根路径
    cd project_dir
    # 进行项目 Git 初始化
    git init
    -
  2. -
  3. 新建项目直接使用 Git 管理
    # 在当前路径下创建项目并使用 Git 初始化项目
    git init project_name
    # 进入项目根路径
    cd project_name
    -
  4. -
-

git clone

-
    -
  • clone
    git clone url
    -
  • -
  • clone 指定分支
    git clone -b branch_name url
    -
  • -
  • clone 指定tag
    # clone 
    git clone url
    # checkout tag
    git checkout tag_name
    -
  • -
  • clone 指定commit
    # 查看git commit 历史的
    git log
    # 指定 commit SHA
    git clone commit_sha_value
    -
  • -
-

git commit

-
git commit -m "注释"
-
-

-a 指定标签名,-m 指定说明文字

-
-

git branch

-
    -
  • 创建分支
    # 创建分支
    git branch branch_name
    # 创建并切换到新分支
    git checkout -b branch_name
    -
  • -
  • 切换分支
    git checkout branch_name
    -
  • -
  • 删除分支
    # 删除本地分支
    git branch -d branch_name
    # 删除远程指定分支
    git push origin --delete branch_name
    -
  • -
  • 重命名分支
    git branch -m old_branch_name new_branch_name
    -
  • -
  • 查看分支
    # 查看本地所有分支
    git branch
    # 查看远程所有分支
    git branch -r
    # 查看本地和远程所有分支
    git branch -a
    -
  • -
-

git tag

-
    -
  • 新增 tag
    git tag -a tag_name -m "注释"
    -
  • -
  • 查看 tag
    # 查看指定 tag 信息
    git show tagname
    # 查看所有 tag
    git tag -l
    -
  • -
  • 删除 tag
    # 删除本地tag
    git tag -d tag_name
    # 删除远程指定tag
    git push origin --delete tag tag_name
    -
  • -
  • 推送 tag 到远程
    # push 单个 tag
    git push origin tag_name
    # push 所有 tag
    git push [origin] --tags
    -
  • -
-

git mv

-
    -
  • 重命名文件
    git mv old_file_name new_file_name
    -
  • -
-

git log

-
    -
  • 查看仓库 commit 历史日志
    # 下面参数可任意组合
    git log --oneline(简洁查看) --all(所有分支) -n4(最近 4 次记录) --graph(图形化展示)
    -
  • -
-

git help

-

更多命令
-

git --help

-

git other

-
    -
  • 查看当前项目远程仓库地址
    git remote -v
    -
  • -
  • 修改仓库地址
    # 方式一:直接修改
    git remote set-url origin [url]
    # 方式二:先删后加
    git remote rm origin
    git remote add origin [url]
    # 方式三:直接修改config文件
    -
  • -
-

附录

-
    -
  • Git Docs
  • -
-]]>
- - Git - - - git bash - -
- - Git 签名 - /2024/06/17/git-signature/ - verified-commit

-

通常 Push 代码到远程托管平台(GitHub,Gitlab,Gitee 等),需要提前在托管平台上传我们 Git 账户的公钥(*.pub),平台使用上传的公钥来验证身份(本地的 Git 私钥与平台上的公钥配对,以确保你有权限读写该仓库),该验证只会在 Push 时进行检查

- -

为什么要签名

-

虽然对 Push 做了检查,但依然不够安全,因为任何拥有该仓库权限的人,都可以在 commit/tag 时使用 git config user.name "假的用户名", git config user.email "假的邮箱地址" 命令来伪造提交者用户信息,这样我们根本无法追溯提交者的身份,所以我们需要给 commit/tag 签名,签名的目的是确保提交的代码在传输和存储过程中没有被篡改,并验证提交者的身份,同时也保证代码的可追溯性

-

commit 签名是在本地,在使用 git commit 命令时进行签名,push 时会将你的签名信息,原封不动 push 到远程仓库

-

commit 签名只是用于验证这条 commit 来自于你本人,与是否有权限操作远程仓库无关

-

如何签名

-

可以使用 GPG、SSH 或 S/MIME,可以在本地对 commit/tag 进行签名。 这些 commit/tag 在 GitHub 上标示为已验证,便于其他人信任更改来自可信的来源

-

SSH、GPG、S/MIME 区别

-
    -
  • SSH 签名是最容易生成的,甚至可以将现有身份验证密钥上传到 GitHub 以用作签名密钥
  • -
  • 生成 GPG 签名比生成 SSH 密钥复杂,但 GPG 具有 SSH 没有的功能,GPG 密钥可以在不使用时过期或撤销
  • -
  • 较大型组织的环境中通常需要 S/MIME 签名
  • -
-

👍 SSH(常用)

-
-

SSH 签名验证需 Git 2.3.4 及以上版本

-
-

检查现有 SSH 密钥

-
# 查看本地 ~/.ssh 路径现有密钥
ls -al ~/.ssh
# 检查输出的列表
-

检查已有密钥列表已有 RSA 密钥,如果你已经有 SHA-2 算法生成 RSA 密钥 那么你可以 跳过 生产新 SSH 密钥,如果没有,为了安全,还是建议重新生成 SHA-2 算法生成 RSA 密钥

-

生产新 SSH 密钥

-
    -
  1. 2022.03.15 -
      -
    • 在该日期 GitHub 删除旧的、不安全的密钥类型来提高安全性
    • -
    • 自该日期起,不再支持 DSA 密钥 (ssh-dss)。 无法github.com 上向个人帐户添加新的 DSA 密钥
    • -
    -
  2. -
  3. 2021.11.02 -
      -
    • 在该日期 之前 带有 valid_after 的 RSA 密钥 (ssh-rsa) 可以继续使用任何签名算法
    • -
    • 在该日期 之后 生成的 RSA 密钥 必须 使用 SHA-2 签名算法。 一些较旧的客户端可能需要升级才能使用 SHA-2 签名
    • -
    -
  4. -
-
-

在生成 SSH 密钥时,我们可以给 SSH 密钥添加密码,也可以不添加密码,这里根据需要选择是否添加密码

-

git-ssh-keygen

-
    -
  1. 执行 ssh-keygen -t ed25519 -C "Jerry.x@outlook.com" 命令,这里的邮箱换成你的 GitHub 邮箱
  2. -
  3. 确认密钥存放位置 -
      -
    • 如果不需调整(默认:/User/blade/.ssh/id_ed25519),可 Enter 键进入下一步
    • -
    • 这里我重命名了密钥 /User/blade/.ssh/id_ed25519_test
    • -
    -
  4. -
  5. 给密钥设置密码 -
      -
    • 如果无需设置,可 Enter 键进入下一步
    • -
    • 如果需设置,输入密码即可
    • -
    -
  6. -
  7. 确认输入的密钥密码,和上一步输入内容一致
  8. -
  9. 提示生成的密钥存放位置和指纹信息
  10. -
-

如果密钥 设置了密码,需要将 SSH 密钥添加到 ssh-agent

-
-

在向 ssh-agent 添加新的 SSH 密钥管理密钥前,应该检查现有 SSH 密钥并生成新的 SSH 密钥

-
-

如果已安装 GitHub Desktop,可使用它克隆存储库,而无需处理 SSH 密钥

-
-
    -
  1. -

    在新的“管理员提升”__ PowerShell 窗口中,确保 ssh-agent 正在运行。 可以使用“使用 SSH 密钥密码”中的“自动启动 ssh agent”说明,或者手动启动它

    -
    # start the ssh-agent in the background
    Get-Service -Name ssh-agent | Set-Service -StartupType Manual
    Start-Service ssh-agent
    -
  2. -
  3. -

    在无提升权限的终端窗口中,将 SSH 私钥添加到 ssh-agent。 如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称

    -
    ssh-add c:/Users/YOU/.ssh/id_ed25519
    -
  4. -
-

将 SSH 密钥添加到该代理时,应使用默认的 macOS ssh-add 命令,而不是使用通过 macports、homebrew 或某些其他外部来源安装的应用程序

-
-
    -
  1. -

    在后台启动 ssh 代理

    -
    $ eval "$(ssh-agent -s)"
    > Agent pid 59566
    -

    根据您的环境,您可能需要使用不同的命令。 例如,在启动 ssh-agent 之前,你可能需要通过运行 sudo -s -H 根访问,或者可能需要使用 exec ssh-agent bashexec ssh-agent zsh 运行 ssh-agent

    -
  2. -
  3. -

    如果你使用的是 macOS Sierra 10.12.2 或更高版本,则需要修改 ~/.ssh/config 文件以自动将密钥加载到 ssh-agent 中并在密钥链中存储密码

    -
      -
    • -

      检查你的 ~/.ssh/config 文件是否在默认位置

      -
    • -
    • -

      如果文件不存在,请创建该文件

      -
    • -
    • -

      打开你的 ~/.ssh/config 文件,然后修改文件以包含以下行。 如果您的 SSH 密钥文件与示例代码具有不同的名称或路径,请修改文件名或路径以匹配您当前的设置

      -
      Host github.com
      # 如果看到了 Bad configuration option: usekeychain 错误,
      # 取消下一行注释,使用 IgnoreUnknown 配置
      # IgnoreUnknown UseKeychain
      AddKeysToAgent yes
      # 如果你选择不向密钥添加密码,应该省略 UseKeychain 行
      UseKeychain yes
      IdentityFile ~/.ssh/id_ed25519
      -
    • -
    -
  4. -
  5. -

    将 SSH 私钥添加到 ssh-agent 并将密码存储在密钥链中。 如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称

    -
    ssh-add --apple-use-keychain ~/.ssh/id_ed25519
    -
  6. -
-

注意

-
-
    -
  1. 当你将 SSH 密钥添加到 ssh-agent 时,--apple-use-keychain 选项会将密码存储在你的密钥链中。 如果选择不向密钥添加密码,请运行命令,而不使用 --apple-use-keychain 选项
  2. -
  3. 选项 --apple-use-keychain 位于 Apple 的 ssh-add 标准版本中。 在 Monterey (12.0) 之前的 macOS 版本中,--apple-use-keychain--apple-load-keychain 标志分别使用语法 -K-A
  4. -
  5. 如果您没有安装 Apple 的 ssh-add 标准版本,可能会收到错误消息。 有关详细信息,请参阅 “错误:ssh-add:非法选项 – apple-use-keychain
  6. -
  7. 如果系统继续提示你输入密码,则可能需要将命令添加到 ~/.zshrc 文件(或 bash 对应的 ~/.bashrc 文件)
  8. -
-
    -
  1. -

    在后台启动 ssh 代理

    -
    $ eval "$(ssh-agent -s)"
    > Agent pid 59566
    -

    根据您的环境,您可能需要使用不同的命令。 例如,在启动 ssh-agent 之前,你可能需要通过运行 sudo -s -H 根访问,或者可能需要使用 exec ssh-agent bashexec ssh-agent zsh 运行 ssh-agent

    -
  2. -
  3. -

    将 SSH 私钥添加到 ssh-agent

    -

    如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称

    -
    ssh-add ~/.ssh/id_ed25519
    -
  4. -
-
-

除了上述的方式生成密钥,还有 为硬件安全密钥生成新的 SSH 密钥 方式,这里不做说明,可以移步官方文档查看

-

将 SSH 密钥添加到 GitHub 账户

-

github-ssh-add

-
-

其他更多设置可参考官方文档 新增 SSH 密钥到 GitHub 帐户

-
-

将签名密钥告诉 Git

-
    -
  • 如需全局配置:添加 --global 参数
  • -
  • 如果你有多个 Git 账号,推荐 按照不同的账号分别进行配置,具体可参考 Git 多账号配置 这篇文章
  • -
-
-
# 1. 配置 Git 使用 SSH 对提交和标记签名
git config gpg.format ssh
# 2. 配置指定 SSH 签名密钥
# 将 ~/.ssh/id_ed25519 替换为要使用的公钥路径
git config user.signingkey ~/.ssh/id_ed25519.pub
# commit 开启自动签名
git config commit.gpgsign true
# tag 开启自动签名
git config tag.gpgsign true
-

配置可信公钥列表

-
mkdir -p ~/.config/git
touch ~/.config/git/allowed_signers
# 将指定的密钥文件(~/.ssh/id_ed25519)内容复制到 allowed_signers 文件中
# 如果是继续追加, 将 > 替换为 >>
cat ~/.ssh/id_ed25519.pub > ~/.config/git/allowed_signers
# 配置可信公钥
git config gpg.ssh.allowedSignersFile "~/.config/git/allowed_signers"
-

对 commit 签名

-
# 1. 当本地分支中的提交更改时,请将 -S 标志添加到 git commit 命令
git commit -S -m "YOUR_COMMIT_MESSAGE"
# 2. 在本地完成创建提交后,将其推送到 GitHub 上的远程仓库
git push
-

对 tag 签名

-
-

注意:如果 Git 客户端配置为默认对提交进行签名,GitHub Desktop 仅支持提交签名

-
-
# 1. 若要对标记进行签名,请将 -s 添加到 git tag 命令
git tag -s MYTAG
# 2. 通过运行 git tag -v [tag-name] 验证已签名的标记
git tag -v MYTAG
-

GPG

-
-

具体实践可参考官方文档 GPG 提交签名验证

-
-

S/MIME

-
-

S/MIME 签名验证需 Git 2.19 及以上版本

-
-

由于 S/MIME 用的比较少,这里就不做具体的演示,可参考 官方文档

-

验证签名

-
# 查看一下本地签名信息
# 正常情况,在 commit 提交号下
# good "git" signature for $(email) with $(publicKey)
git log --show-signature
-

问题

-

验证签名提示异常

-

No signature:

-
    -
  • 表示 Git 不知道要信任哪些 SSH 密钥
  • -
  • 解决方法:配置可信公钥列表
  • -
-

no-signature

No principal matched

-

no-principal

invalid key

-

invalid-key

-

如何给现有密钥更新密码

-

通过输入以下命令,您可以 更改 现有私钥 的密码而无需重新生成密钥对

-
# 修改 id_ed25519 密钥密码
$ ssh-keygen -p -f ~/.ssh/id_ed25519
> Enter old passphrase: [Type old passphrase]
> Key has comment 'your_email@example.com'
> Enter new passphrase (empty for no passphrase): [Type new passphrase]
> Enter same passphrase again: [Repeat the new passphrase]
> Your identification has been saved with the new passphrase.
-

参考

-
    -
  1. Git 提交使用 SSH 签名和 GPG 签名验证
  2. -
  3. SSH 提交签名验证
  4. -
  5. GitHub commit 签名指南
  6. -
  7. 维护代码的尊严:GPG签名让你的Git commit不再裸奔
  8. -
-]]>
- - Git - - - git signature - -
- - Git 子仓库管理 - /2018/05/17/git-sub/ - 在使用 NexT 作为 Hexo 博客的主题时,不能 友好 的支持其主题的更新,以及 多设备 之间的主题同步。

-

按照官方提供的导入主题操作指引

-
$ cd hexo
$ git clone https://github.com/theme-next/hexo-theme-next themes/next
-

发现commit并push到GitHub的远程服务器上,发现 themes/next 路径下并不能打开和查看该路径下的文件,原因是NexT是当前项目的一个子仓库(项目),在 Github 上对于之仓库项目的引用,推荐使用 git subtree 命令来进行对子仓库的管理,不推荐直接拷贝需要子仓库的代码到自己的项目中

- -

原因是我是使用 Travis CI 来部署自己的项目,具体的构建脚本和介绍请看,下面分别使用 git submodulegit subtree 的方式进行 NexT 主题的管理

-

git submodule 与 git subtree

-

git submodulegit subtree都可以实现一个仓库作为其他仓库的子仓库的管理

-
-
    -
  • git submodule:是 Git 官方以前的推荐方案
  • -
  • git subtree:Git 1.5.2 开始,Git 新增并推荐使用这个功能来管理子项目
  • -
  • git subtreegit submodule不同,它不增加任何像 .gitmodule 这样的新的元数据文件
  • -
  • git subtree对于项目中的其他成员透明,意味着可以不知道 git subtree 的存在
  • -
-

git submodule 常用操作

-

Git Submodule功能官方操作指引

-

add 一个submodule

-
    -
  1. -

    Fork Repository
    -hexo-theme-next项目右上角 Fork 按钮即可

    -
  2. -
  3. -

    Clone Repository

    -
    git clone git@github.com:RootCluster/hexo-theme-test.git
    -
  4. -
  5. -

    Add Submodule

    -
    # 进入项目
    cd hexo-theme-test
    # 注册 next 项目是一个submodule,并把数据拷贝到 `themes/next` 路径
    git submodule add git@github.com:RootCluster/hexo-theme-next.git themes/next
    -
  6. -
  7. -

    status

    -
    # 当前 submodule 已被注册并指向了某个 commit
    git submodule status
    1f5643061ec5257269673bd6159403c24015c53d themes/next (v6.3.0)
    # 查看在父仓库中有哪些变化被注册
    git status
    On branch submodule
    Changes to be committed:
    (use "git reset HEAD <file>..." to unstage)
    new file: .gitmodules
    new file: themes/next
    -
    -

    有2个文件被修改过:.gitmodules,themes/next,当在父仓库时,Git 不会跟踪 submodule 中的文件,Git 只把它当成一个单一的文件

    -
    -
      -
    • .gitmodules:存有 submodule 的信息
    • -
    • themes/next:submodule 它自己
    • -
    -
  8. -
  9. -

    commint

    -
    # 推送到远程 submodule 分支
    git commit -am "add next submodule"
    [submodule a5a612b] add next submodule
    2 files changed, 4 insertions(+)
    create mode 100644 .gitmodules
    create mode 160000 themes/next
    -
  10. -
  11. -

    push

    -
    git push origin submodule
    Counting objects: 4, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (4/4), done.
    Writing objects: 100% (4/4), 451 bytes | 451.00 KiB/s, done.
    Total 4 (delta 1), reused 0 (delta 0)
    remote: Resolving deltas: 100% (1/1), completed with 1 local object.
    To github.com:RootCluster/hexo-themes-test.git
    71879a8..a5a612b submodule -> submodule
    -
  12. -
-

查看 Github 上的仓库,发现父仓库里有一个指向 submodule 的链接,表示你已经成功添加了一个 submodule

-

clone 带 submodule 的项目

-

新路径下,clone 项目,submodule 分支

-
# clone 项目
git clone -b submodule git@github.com:RootCluster/hexo-themes-test.git
# 进入项目路径
cd hexo-themes-test/
# 项目注册 submodule
git submodule init
# clone submodule 代码
git submodule update

-

update 带 submodule 的项目

-

只要在 submodule 路径下,所有的常规Git操作,如 push, pull, reset, status 等,都可以正常工作,如果要保证 submodule 和远程仓库保存同步,在 submodule 路径下运行 git pull

-
    -
  • 如果你得到一个错误信息, 说你不在任何分支之上, 只要运行 git checkout master 就可修复
  • -
  • 如果你在 pullsubmodule 有一些更新, 父仓库会告诉你有一些变动需要 commit 了, submodule自身指向一个指定的 commit, 并且如果这个 commit 改变了, 父仓库会得知这个改变. 如果你的 submodule 需要在一个指定 commit 上工作, 可用 git reset 来设置
  • -
-

例如:我需要把NexT的版本改变到上一个 Tag 6.2.0 (目前是6.3.0)

-
-

git reset --hard (commit hash)

-
-
# 进入项目路径
cd hexo-themes-test/
# 重新指向 submodule 关联的 commit 记录
git reset --hard 206d463
# 回到父目录
cd ..
# commit 本次的修改
git commit -am "set next version to 6.2.0"
-

推送到远程仓库后,submodule 会和指定的commit 关联起来。如果你和别人一起工作在同一个项目,别人也可以在submodulepull并且commit,因此改变了submodulecommit指向,这个问题,可以通过git reset 来解决

-
-

remove 项目中的 submodule

-
    -
  • 项目的根目录下(不是 submodule 的目录),编辑 .gitmodules 文件,删除submodule配置
    [submodule "themes/next"]
    path = themes/next
    url = https://github.com/RootCluster/hexo-theme-next.git
    -
  • -
  • 项目根目录下,编辑 .git 文件夹下 config 文件,删除 submodule 配置
    [submodule "themes/next"]
    url = https://github.com/RootCluster/hexo-theme-next.git
    -
  • -
  • 清除 submodule 缓存
    git rm --cached themes/next
    -
  • -
-

git subtree 常用操作(重点)

-

add一个subtree

-
    -
  • -

    在父仓库中新增子仓库

    -
    # 添加子仓库
    git subtree add --prefix=themes/next https://github.com/RootCluster/hexo-theme-next.git master --squash
    git fetch https://github.com/RootCluster/hexo-theme-next.git master
    warning: no common commits
    remote: Counting objects: 3407, done.
    remote: Total 3407 (delta 0), reused 0 (delta 0), pack-reused 3406
    Receiving objects: 100% (3407/3407), 1.21 MiB | 36.00 KiB/s, done.
    Resolving deltas: 100% (2192/2192), done.
    From https://github.com/RootCluster/hexo-theme-next
    * branch master -> FETCH_HEAD
    Added dir 'themes/next'
    -
    -

    --squash参数表示不拉取历史信息,而只生成一条 commit 信息

    -
    -
  • -
  • -

    查看项目状态

    -
    # 查看项目状态
    git status
    On branch subtree
    Your branch is ahead of 'origin/subtree' by 2 commits.
    (use "git push" to publish your local commits)

    nothing to commit, working tree clean
    -
  • -
  • -

    推送更改到远程仓库

    -
    git push origin subtree
    Counting objects: 381, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (334/334), done.
    Writing objects: 100% (381/381), 650.26 KiB | 34.22 MiB/s, done.
    Total 381 (delta 23), reused 225 (delta 19)
    remote: Resolving deltas: 100% (23/23), completed with 1 local object.
    To https://github.com/RootCluster/hexo-themes-test.git
    8ed2e2e..405af42 subtree -> subtree
    -
  • -
-

pull 子仓库更新

-
# 更新子仓库
git subtree pull --prefix=themes/next https://github.com/RootCluster/hexo-theme-next.git master --squash
From https://github.com/RootCluster/hexo-theme-next
* branch master -> FETCH_HEAD
Subtree is already at commit 1f5643061ec5257269673bd6159403c24015c53d.
-

push 子仓库修改

-

在引用子仓库的项目中修改了子仓库的相关代码,推送修改到源仓库

-
    -
  • commit 修改记录
  • -
  • push 到源仓库
    # 推送子仓库修改到源仓库master分支
    git subtree push --prefix=themes/next https://github.com/RootCluster/hexo-theme-next.git master
    -
  • -
-

subtree 常用命令

-
git subtree add   --prefix=<prefix> <commit>
git subtree add --prefix=<prefix> <repository> <ref>
git subtree pull --prefix=<prefix> <repository> <ref>
git subtree push --prefix=<prefix> <repository> <ref>
git subtree merge --prefix=<prefix> <commit>
git subtree split --prefix=<prefix> [OPTIONS] [<commit>]
-

附录

-
    -
  • 如何使用 Git Submodule
  • -
  • git subtree教程
  • -
-]]>
- - Git - - - git subtree - git submodule - -
- - .gitignore 基础知识 - /2018/04/13/gitignore/ - .gitignore顾名思义是Git中用来管理所需要忽略或者说不用纳入版本控制文件

-

基本配置语法

-
    -
  1. “#“:表示注释
  2. -
  3. “/“:表示目录
  4. -
  5. “*“:表示通配符,用来通配多个字符
  6. -
  7. “?“:表示通配单个字符
  8. -
  9. “[]“:表示包含单个字符的匹配列表
  10. -
  11. “!“:表示不忽略匹配到的文件或者目录
  12. -
- -
-

注意:Git对.gitignore配置文件是从上往下进行规则匹配,这也意味如果:前(limit)>后(limit),则后面的规则不会被执行

-
-

全局与局部

-

.gitignore分为: 全局 ignore,局部 ignore

-

全局ignore设置

-
    -
  • 在用户账户文件夹(C:\Users<‘YourName’>)路径下新建一个命名为.gitignore_global的文件
  • -
  • 使用Git Bash(需要切换路径到C:\Users<‘YourName’>)或者Git CMD命令行工具输入:
    git config --global core.excludesfile ~/.gitignore_global
    -
  • -
  • 此时全局ignore已经设置完成,你只需要修改.gitignore_global文件内需要忽略的文件类型就可以全局控制忽略不需要纳入版本控制的文件或文件夹
  • -
  • 不难发现,其实是往 .gitconfig中加入如下内容来指名Git忽略不纳入版本控制的文件,当然如果你不想用命令行完成全局设置,你也可以直接在.gitconfig文件中加入[core] excludesfile= ~/.gitignore_global内容即可
  • -
-

局部ignore设置

-
    -
  • 只需要在Git控制版本控制项目的根目录中加入.gitignore文件,在.gitignore文件中写明忽略不纳入版本控制的文件即可
  • -
-

参考示例

-
-

你可以查看参考Github官方所写好的示例

-
-

插件.ignore

-

支持Android Studio,JetBrains系列
-安装方法

-
    -
  • Settings > Plugs > Browse repositories > .ignore > Install plugin
  • -
  • 里面有已经写好的模板,只需适当修改
  • -
-]]>
- - Git - - - ignore - -
- - Gitlab 应用搭建 - /2018/04/24/gitlab1/ - 我司团队之前一直使用SVN来进行代码托管,主要问题

-
    -
  1. 每次来个新人都需要找对应的SVN管理员进行授权分配指定的仓库操作权限,有时候需要多个项目切换,还得再次提出进行仓库的指定
  2. -
  3. SVN都是以中文命名,这其实没啥,但是在eclipse 以及IDEAXcode等开发工具,链接地址都会把中文字进行编码,造成路径非常的长,强迫症的我这怎么忍得了
  4. -
  5. 产品相关的,设计相关的啥也都放在SVN里面,搞得SVN里面鱼龙混杂
  6. -
- -

因此在我提出及建议下,部门经理同意了对代码的管理进行隔离方便有效的对代码的授权监管,并同时制定代码的相关规范和服务的自动化部署等,提高团队的开发效率和代码质量。

-

本节主要介绍Gitlab的环境搭建和基础的功能配置

-

目的:

-
    -
  1. 搭建Gitlab服务
  2. -
  3. 和公司AD域账号关联,用域账号直接登录Gitlab
  4. -
  5. 挂载Gitlab 仓库到指定存储位置
  6. -
-

Gitlab安装

-

环境

-
    -
  • OS:CentOS 7
  • -
  • Gitlab:Gitlab CE 10.6.4
  • -
-
-

Gitlab 版本

-
-
    -
  • Gitlab Community Edition (CE):社区版,免费,用户自行托管,通过社区提供技术支持
  • -
  • Gitlab Enterprise Edition (EE):企业版,付费,用户自行托管,提供附加的功能以及技术支持
  • -
  • Gitlab.com:免费的SaaS服务,可以创建共有以及私有的版本库,可以购买额外的技术支持
  • -
  • GitHost.io:由Gitlab提供的用户私有的独享服务
  • -
-

Gitlab部署

-
    -
  1. -

    系统防火墙中打开HTTP和SSH访问

    -
    sudo yum install -y curl policycoreutils-python openssh-server
    sudo systemctl enable sshd
    sudo systemctl start sshd

    sudo firewall-cmd --permanent --add-service=http
    sudo systemctl reload firewalld
    -
  2. -
  3. -

    安装Postfix发送通知邮件。如果您想使用其他解决方案发送电子邮件,请跳过此步骤并在安装GitLab后配置外部SMTP服务器

    -
    sudo yum install postfix
    sudo systemctl enable postfix
    sudo systemctl start postfix
    -
  4. -
  5. -

    添加GitLab软件包存储库

    -
    curl -LJO https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-10.0.0-ce.0.el7.x86_64.rpm
    -
  6. -
  7. -

    安装软件包

    -
    rpm -i gitlab-ce-10.0.0-ce.0.el7.x86_64.rpm
    -

    完成安装如下日志显示:

    -
         *.                  *.
    *** ***
    ***** *****
    .****** *******
    ******** ********
    ,,,,,,,,,***********,,,,,,,,,
    ,,,,,,,,,,,*********,,,,,,,,,,,
    .,,,,,,,,,,,*******,,,,,,,,,,,,
    ,,,,,,,,,*****,,,,,,,,,.
    ,,,,,,,****,,,,,,
    .,,,***,,,,
    ,*,.



    _______ __ __ __
    / ____(_) /_/ / ____ _/ /_
    / / __/ / __/ / / __ \`/ __ \
    / /_/ / / /_/ /___/ /_/ / /_/ /
    \____/_/\__/_____/\__,_/_.___/

    -
  8. -
  9. -

    编译配置文件

    -
    cd /opt/gitlab/bin
    ./gitlab-ctr reconfigure
    -
  10. -
  11. -

    启动服务

    -
    ./gitlab-ctl start
    -
    -
      -
    • 成功启动服务,默认路径访问:http://localhost:80
    • -
    • 默认安装位置 /opt/gitlab/
    • -
    • 配置文件默认路径 /etc/gitlab/gitlab.rb
    • -
    • 默认账号:root,密码:5iveL!fe
    • -
    -
  12. -
-

常用配置项修改

-

以下配置项的修改,完成后均需要重新编译文件(配置文件默认路径 /etc/gitlab/gitlab.rb),默认,并重启Gitlab服务

-

访问地址

-

修改external_url为Gitlab对应机器IP所配置的域名
-gitlab-url

-

LDAP启用

-

修改host,port,bind_dn,password,base参数即可
-gitlab-ladp

-

各参数解释:

-
    -
  • hostport 是 LDAP 服务的主机地址及端口
  • -
  • bind_dn 和 password 是一个管理 LDAP 的 dn 及密码
  • -
  • base 表示 LDAP 将以该 dn 为 节点,向下查找用户
  • -
  • user_filter 表示以某种过滤条件筛选用户
  • -
  • attributes 表示 GitLab 中的字段与 LDAP 中哪些字段可以相互对应,比如可以用 LDAP 中的 uid 来作为 GitLab 用户名
  • -
-

编译重启后,查看登录是否已经显示LDAP登录入口

-

gitlab-ldap-login

-

为了安全我们需要关闭 GitLab 自己的注册功能,这样新用户只能通过 LDAP 认证的方式进行登陆。

-

gitlab-sign-up

-

存储仓库修改

-

默认仓库存储位置:/var/opt/gitlab/git-data/repositories/
-gitlab-dirs

-

Gitlab日志

-

默认日志位置: /var/log/gitlab

-
cd /opt/gitlab/bin
gitlab-ctl tail -f nginx/gitlab_access.log
-

或者在Gitlab服务的系统设置中查看
-gitlab-logs

-

附录

-
    -
  • 官方安装教程
  • -
  • 官方配置文件
  • -
-]]>
- - Git - - - Gitlab - -
- - Gradle(一)基础 - /2020/12/10/gradle1/ - GitHub 上 Gralde 是这样描述,“Adaptable, fast automation for all”(让一切都能快速自动化
-Gradle是一个构建工具,专注于构建自动化和对多语言开发的支持。对于在任何平台上的构建,测试,发布和部署,Gralde 提供了一种灵活的模型,可以支持从编译和打包代码到发布网站的整个生命周期。Gralde 旨在支持跨多种语言和平台的构建自动化,包括 Java,Scala,Android,Kotlin,C/C++ 和 Groovy,并于开发工具和包括 Eclipse,IntelliJ 和 Jenkins 的持续集成服务器紧密集成

- -
    -
  • Gradle official:https://gradle.org
  • -
  • Gradle docs:https://docs.gradle.org
  • -
  • Gradle plugins:https://plugins.gradle.org
  • -
-

Gradle 特点

-
    -
  1. Gradle 基于 JVM 的构建工具
  2. -
  3. 兼容支持 Maven,Ant 等
  4. -
  5. 支持基于 Groovy 的构建脚本
  6. -
  7. 编译构建执行效率更高
  8. -
  9. 支持多种语言等
  10. -
  11. 易于迁移
  12. -
-

Gradle 安装配置

-
    -
  • Gradle 官方:https://services.gradle.org/distributions/
  • -
  • Tencent 镜像:https://mirrors.cloud.tencent.com/gradle/
  • -
-
-

Tencent Gradle 镜像同步有一定的延迟,需要注意

-
-

下载

-

下载需要的版本即可,推荐最新版,这里以最新稳定版 6.7.1 为例,每个正式版本包含如下文件,我们选择 xxx-bin.zip(二进制版,只包含了二进制文件(可执行文件),没有文档和源代码) 或 xxx-all.zip(完整版,包含了各种二进制文件,源代码文件,和离线的文档)的文件即可,进行手动安装

-
gradle-6.7.1
├── gradle-6.7.1-wrapper.jar.sha256 # wrapper.jar hash 校验文件
├── gradle-6.7.1-docs.zip # gradle 文档压缩文件
├── gradle-6.7.1-docs.zip.sha256 # gradle 文档 hash 校验文件
├── gradle-6.7.1-src.zip # gradle 源码版,只包含了 Gradle 源代码,不能用来编译你的工程
├── gradle-6.7.1-src.zip.sha256 # gradle 源码版 hash 校验文件
├── gradle-6.7.1-bin.zip # gradle 核心压缩文件
├── gradle-6.7.1-bin.zip.sha256 # gradle 核心 hash 校验文件
├── gradle-6.7.1-all.zip # gradle 全部资源压缩文件
└── gradle-6.7.1-all.zip.sha256 # gradle 全部资源 hash 校验文件
-

当然如果你使用的 macOS 系统,且也已经安装了 homebrew 包管理工具,那么同样你也可以使用 brew 命令来安装 Gradle,那么你将不需要再去手动配置 Gradle 的环境,它的安装默认路径在 /usr/local/bin/gradle,安装完成后你就可以使用 gradle 的相关命令

-
# gradle 安装
brew install gradle
# gradle 升级
brew upgrade gradle
# 检查是否安装成功
gradle -v
-

配置

-

手动下载解压的文件进行安装,则需要配置 Gradle 的环境,这样方便我们在任何地方都可以调用 Gradle 的命令,对于 macOS 上手动安装配置 Gradle 环境的操作,可以参考 MacBook Pro 初始化 这篇文章 Gradle 配置

-

对于 Windows 系统,按照如下步骤进行添加环境变量,我这里 Windows 上为了和项目中 Gradle 版本有所区分,配置的是 6.7 版本
-
-
-配置完成后,老规矩我们需要验证下我们的配置是否生效,在命令行中输入 gradle -v 命令,查看有 Gradle 相关的版本信息提示,我们的配置就已成功
-

-

GRADLE_HOME

-

GRADLE_HOME 这个环境变量,它主要是我们手动配置指定 GRADLE 使用的命令环境

-

GRADLE_USER_HOME

-

GRADLE_USER_HOME 指配置 Gradle 的安装下载的路径。默认 /Users/<PC NAME>/.gradle 路径,如果你在系统环境中设置了 GRADLE_USER_HOME 的环境变量,那么下载的路径就变成了你自定义设置的路径

-

Gradle 基础

-

刚刚在上面我们配置时,使用了 gradlew 命令,那这个又是啥呢,这里简单解释下,gradlew 是 gradle wrapper 的简写,对于 Gradle 构建的项目,用于解决 Gradle 安装,部署以及统一项目的 Gradle 的构建版本等一系列问题。

-

Gradle 有两个基本的概念:project 和 task,Gradle 里面的所有东西基于这两个概念

-
    -
  • project:通常指一个项目
  • -
  • task:指构建过程中的任务
  • -
-

一次构建可以有 1 到 n 个 project,而每个 project 有 1 到 n 个 task

-

Gradle 项目

-

Android 项目工程一开始就默认使用 Gradle 来构建,在 Android 领域里使用花样也是比较多,更好体现了 Gradle 的灵活性,对于后端 Spring 系列项目,现在也是越来越多的开始使用 Gradle 来构建了,在 Spring Boot 2.3.0.M1 版本官方已开始在生产环境开始使用 Gradle 代替 Maven 进行构建,测试,发布项目。这从侧面也印证了 Gradle 对于复杂庞大的系统更加友好和高效

-

对于使用 Gradle 构建的 Android 项目也好,Java 项目也好,还是 SpringBoot 项目也罢,它们都有共同的特点。在结构上有下面的相同点

-
project
├── ……
├── .gradle/ # 项目使用 gradle 编译生成的临时文件存放位置
├── gradle/wrapper
│ │── gradle-wrapper.jar # gradlew 核心执行文件
│ └── gradle-wrapper.properties # gradle 运行环境配置文件
├── build.gradle # 项目依赖配置,脚本配置文件
├── gradlew # Linux or macOS 下可执行脚本
├── gradlew.bat # Windows 下可执行脚本
├── settings.gradle # 配置构建应用时应将哪些模块包含在内
└── ……
-

gradle-wrapper.properties

-
    -
  • gradle-wrapper.jar 文件是项目中执行 gradlew 相关命令的具体实现,感兴趣的可以查看其中的具体源码实现
  • -
  • gradle-wrapper.properties 是 Gradle 项目版本管理的核心 -
      -
    • distributionBase=GRADLE_USER_HOME:指定了 wrapper 保存下载的 Gradle 的主路径
    • -
    • distributionPath=wrapper/dists:指定了 wrapper 保存下载的 Gradle 的子路径
    • -
    • zipStoreBase=GRADLE_USER_HOME:指定了 wrapper 保存下载 gradle-6.7.1-bin.zip 文件的主路径
    • -
    • zipStorePath=wrapper/dists:指定了 gradle-6.7.1-bin.zip 文件的子路径
    • -
    • distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-bin.zip:gradle 文件下载的源地址
    • -
    -
  • -
-

distributionBase 和 zipStoreBase 有两种取值

-
    -
  • GRADLE_USER_HOME:默认使用方式,表示用户目录,默认路径 /Users/<PC NAME>/.gradle
  • -
  • PROJECT:表示工程的当前目录,不常用
  • -
-
-

对应 Gradle 的下载及解压目录这里还需要注意下

-

Gradle 的存放地址,比如:~/.gradle/wrapper/dists/gradle-6.7.1-bin/bwlcbys1h7rz3272sye1xwiv6 这里一个看起来无规则的文件夹,我们的 gradle 下载及解压必须放在这个文件夹内,而这个看似无规则的文件夹,实质是根据 distributionUrl 路径字符串计算 md5 值得来的

-
-

build.gradle 及 settings.gradle

-

对于build.gradlesettings.gradle 文件在 Android 应用和 SpringBoot 应用是不一样,因此关于他两介绍请移步 Gradle(二)AndroidGradle(三)SpringBoot 文章进行查看

-

Gradle 依赖

-

用于声明依赖关系的配置

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
配置名称角色是否可消费是否可分解描述
api声明API依赖项NN在这里,您可以声明依赖关系,这些依赖关系会在编译时和运行时以可传递方式导出到使用者
implementation声明实现依赖性NN在这里,您可以声明纯属内部的依赖关系,而不是要向使用方公开(在运行时仍向使用方公开)
compileOnly声明仅编译依赖项NN在这里可以声明在编译时需要的依赖项,而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项
compileOnlyApi声明仅编译API依赖项NN在这里,您可以声明模块和使用者在编译时需要的依赖项,而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项
runtimeOnly声明运行时依赖项NN在这里可以声明仅在运行时才需要的依赖关系,而在编译时则不需要
testImplementation测试依赖NN在这里声明用于编译测试的依赖项
testCompileOnly声明测试仅编译依赖项NN在这里声明仅在测试编译时需要的依赖项,而不应泄漏到运行时。这通常包括在运行时找到时会被阴影化的依赖项
testRuntimeOnly声明测试运行时依赖项NN在这里可以声明仅在测试运行时才需要的依赖项,而在测试编译时则不需要
-

核心需要掌握的是 apiimplementationcompileOnlyruntimeOnly 这4种依赖方式

-
-

对于你可能看到依赖方式,compile(api),provided(compileOnly),apk(runtimeOnly) 这些方式是比较旧的依赖方式,在 gradle plugin 3.0 开始已废弃,请使用新的依赖方式

-
-

本地依赖

-

本地依赖 module lib

-

通过这种方式依赖的弊端是每次都需要构建 module,但 module 比较多时构建非常耗时,建议控制 module 的依赖数量,避免构建耗时

-
// module 需要在项目根目录下的 settings.gradle 中通过 include 引入
implementation project(':libname')
-

本地二进制 lib 依赖

-

本地的 jar 或者 aar 需要放在 module 的 libs 文件夹下,通过这种方式依赖

-
依赖 jar
-
// 方式一:可以一次性依赖 libs 下的所有 jar
implementation fileTree(dir: 'libs', include: ['*.jar'])

// 方式二:可以指定依赖一个或几个 jar
implementation files('libs/xxxx1.jar', 'libs/xxxx2.jar')
-
依赖 aar
-
// 在 module 的 build.gradle 中添加目录指定
repositories {
flatDir {
dirs 'libs'
}
}

// 在 dependencies 中加入对 aar 的引入
// 方式一:可以一次性依赖 libs 下所有的 aar
implementation fileTree(dir: 'libs', include: ['*.aar'])
// 方式二:可以指定依赖某一个aar
implementation files(name: 'aar-lib-name', ext: 'aar')
-

远程二进制 lib 依赖

-
// 依赖明确的版本,标明 group、name 和 version
implementation group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.1.1'

// 常用的简写方式引用
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
-

Gradle 命令

-

在项目中推荐使用 gradlew 命令来进行执行,这样本质是使用项目所依赖的 Gradle 版本进行执行。当然如果你本地配置了 Gradle 的环境变量,你可以将 gradlew 命令更改成 gradle 来执行

-
    -
  • gradlew clean: 清除 build 文件夹
  • -
  • gradlew check: 执行 lint 检查
  • -
  • gradlew assemble: 编译并打包你的代码,但并不运行单元测试
  • -
  • gradlew build: 编译和测试你的代码,并生成一个包含所有类与资源的文件
  • -
  • gradlew dependencies: 查看所有依赖库 -
      -
    • gradlew dependencies -configuration runtime: 查看运行时依赖库
    • -
    -
  • -
-

注意:

-
    -
  • Windows:在项目根目录,使用的是 gradlew
  • -
  • Linux or macOS:在项目的根目录,使用的是 ./gradlew
  • -
-
-

总结

-
    -
  1. 对于 Gradle 我们不需要配置 GRADLE_USER_HOME 的环境,原因是项目中已对使用 Gradle 的版本做出了统一,我们仅需要根据自身的网络需要(如果从默认地址下载很慢,则需要配置好项目依赖镜像源)做出合适的配置。而如果你需要在任何地方使用 gradle 相关的命令,则配置 GRADLE_HOME 即可
  2. -
  3. 依赖方式,我们选择 implementation 方式,这样可屏蔽掉不同应用之间因为引用了同一 lib 而不同版本造成的麻烦问题等
  4. -
-

参考

-
    -
  • gradle-wrapper.properties中各属性的含义
  • -
  • Dependency management in Gradle
  • -
  • The Java Library Plugin
  • -
  • The Distribution Plugin
  • -
-]]>
- - Gradle - - - Gradle - -
- - Gradle(二)Android - /2020/12/15/gradle2/ - 在上一篇 Gradle 的文章中,已经对 Gradle 有了一定的认识,Gradle 在 Android 有着广泛的应用,用作 Android 包依赖管理,应用构建,测试,等一些列自动化,我们本篇就来了解下在 Android 领域 Gradle 的使用。其实 Android 项目结构和之前在第一篇 Gradle 项目结构基本相同,只是在 module 级别多了的 proguard-rules.pro。对于不管是 Android 项目或是 Spring 系列项目的子 module 都会有 build.gradle 文件

- -

Project 级别

-

build.gradle

-
// gradle 脚本执行所需依赖,分别是对应的maven库和插件
buildscript {
repositories {
google()
jcenter()
}
// 声明依赖 Android Gradle 插件版本
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

// 项目所有 module 配置需要的依赖
allprojects {
repositories {
google()
jcenter()
}
}

// 一个 clean 任务,用于删除 build 目录的文件
task clean(type: Delete) {
delete rootProject.buildDir
}
-

settings.gradle

-
// 默认指的是创建 Android 项目生成的 app 模块,也是默认的应用启动模块
include ':app'
-

Module 级别

-
// 表示这是一个应用程序模块,可直接运行
apply plugin: 'com.android.application'

// 编译时间
static def releaseTime() {
return new Date().format('yyyy-MM-dd', TimeZone.getTimeZone('UTC'))
}

android {
// 编译 Android 版本
compileSdkVersion 29
// 默认配置
defaultConfig {
// 应用 ID,手机中用于识别应用的唯一标识
applicationId "org.incoder.android"
// 目标 Android 版本
targetSdkVersion 29
// 申明应用可超过 65536 的方法,可参考:https://developer.android.google.cn/studio/build/multidex?hl=zh_cn
multiDexEnabled true
// 申明要使用AndroidJUnitRunner进行单元测试
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

// 签名配置,相关信息放置在 gradle.properties 文件中
signingConfigs {
debug {
storeFile file(DEBUG_STORE_FILE)
storePassword DEBUG_STORE_PASSWORD
keyAlias DEBUG_KEY_ALIAS
keyPassword DEBUG_KEY_PASSWORD
}
release {
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
v2SigningEnabled true
}
}

buildTypes {
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
// 签名
// signingConfig signingConfigs.debug
manifestPlaceholders = [
//JPush
JPUSH_APPKEY : "",
JPUSH_CHANNEL: "",
// Pgy
PGYER_APPID : "7907554687e4c116316feedb3820ce52",
// Bugly
BUGLY_APPID : "",
VERSION_NAME : "0.1.0",
]
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}

release {
// 混淆
minifyEnabled false
// Zipalign优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources false
// 前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// 签名
signingConfig signingConfigs.release
// AppAnalytics key
manifestPlaceholders = [
// JPush
JPUSH_APPKEY : "",
JPUSH_CHANNEL: "",
// Pgy
PGYER_APPID : "7907554687e4c116316feedb3820ce52",
// Bugly
BUGLY_APPID : "",
VERSION_NAME : "0.1.0",
]
}
}

// 重命名安装包
android.applicationVariants.all {
variant ->
variant.outputs.all {
output ->
output.outputFileName = variant.flavorName + buildType.name +
"_" + releaseTime() + ".apk"
}
}

// 产品变种
flavorDimensions "minSDK"

// 针对不同渠道的配置
productFlavors {
// 测试环境渠道包
dev {
applicationId 'org.incoder.test'
minSdkVersion 19
// 测试环境IP配置 API 接口地址
buildConfigField 'String', 'API', '"http://xxx.xxx.xxx.xxx:8888"'
versionCode 2020122501
versionName "2.0"
// 指定产品变种
dimension "minSDK"
}
// 正式环境渠道包
rel {
applicationId "org.incoder.android"
minSdkVersion 16
// 正式环境域名 API 接口地址
buildConfigField 'String', 'API', '"http://api.xxx.xxx/"'
versionCode 2020122501
versionName "2.1"
// 指定产品变种
dimension "minSDK"
}
}

// 过滤指定产品变种(渠道,构建类型)
// variantFilter { variant ->
// def names = variant.flavors*.name
// def isDebug = variant.buildType.debuggable
// // To check for a certain build type, use variant.buildType.name == "<buildType>"
// if (names.contains("rel") && isDebug) {
// // Gradle ignores any variants that satisfy the conditions above.
// setIgnore(true)
// }
// }

// 多渠道配置
productFlavors.all {
flavor ->
flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
}

// 执行lint检查,有任何的错误或者警告提示,都会终止构建
lintOptions {
disable 'MissingTranslation', 'ExtraTranslation'
// abortOnError一定要设为false,这样即使有报错也不会停止打包了
abortOnError false
// 在打包Release版本的时候进行检测,可以打开,这样报错还会显示出来
checkReleaseBuilds false
}

dexOptions {
jumboMode true
javaMaxHeapSize "4g"
}

// 打包时的配置
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/rxjava.properties'
}

aaptOptions.cruncherEnabled = false
aaptOptions.useNewCruncher = false

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

// 项目依赖的包
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:support-v13:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:support-vector-drawable:28.0.0'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
testImplementation 'junit:junit:4.12'
}
-

关于 module 中的 build.gradle 配置文件中的各项已在示例中加入了注释说明,其中一些配置,这里再简单的说明下

-

apply plugin

-

这里的 apply plugin 有两种模式:

-
    -
  1. com.android.application:表示这是一个应用程序模块
  2. -
  3. com.android.library:表示这是一个库模块
  4. -
-

前者可以直接运行,后着是依附别的应用程序运行

-

buildTypes

-

这里主要是生成安装文件的配置信息,一个 debug 类型,用于指定生成测试版安装文件配置,可忽略不写;另一个是 release,用于指定生成正式版安装文件的配置。

-
    -
  • minifyEnabled:是否对代码进行混淆,默认 false
  • -
  • proguardFiles:指定混淆的规则文件,默认指定了 proguard-android.txt 文件和 proguard-rules.pro 文件。 -
      -
    • proguard-android.txt:默认的混淆文件,里面定义了一些通用的混淆规则
    • -
    • proguard-rules.pro:位于当前项目的根目录下,可以在该文件中定义一些项目特有的混淆规则
    • -
    -
  • -
  • buildConfigField:可用于解决不同渠道不同的服务地址,或不同渠道 LOG 打印控制等
  • -
  • debuggable:是否支持断点调试,release 默认为 false,debug 默认 true
  • -
  • jniDebuggable:是否可以调试 NDK 代码,使用 lldb 进行 C 和 C++ 代码调试,release 默认为 false
  • -
  • signingConfig:设置签名信息,通过 singingConfig.release 或 singingConfig.debug,配置相应的签名,但是添加此配置前需要先添加 singingConfig 闭包
  • -
  • renderscriptDebuggable:是否开启渲染脚本,就是一些 C 写的渲染方法,默认为 false
  • -
  • renderscriptOptimLevel:渲染等级,默认为 3
  • -
  • zipAlignEnabled:是否对 apk 包执行 zip 对齐优化,减少 zip 体积,提高运行效率,release 和 debug 都默认 true
  • -
  • pseudoLocalesEnabled:是否在 apk 中生成伪语言环境,帮助国际化,一般很少使用
  • -
  • applicationIdSuffix:和 defaultConfig 中配置一样,指在 applicationId 中添加一个后缀
  • -
  • versionNameSuffix:添加版本名称的后缀,一般使用较少
  • -
-

productFlavors

-

这个配置主要是解决应用发布在不同应用市场,而需要对不同应用市场做一些不同配置,比如包名,应用名,以及一些统计,而需要不同渠道统计 ID 等

-

packagingOptions

-

packagingOptions 常见的设置项有 exclude、pickFirst、doNotStrip、merge

-
    -
  1. exclude:过滤掉某些文件或者目录不添加到APK中,作用于APK,不能过滤aar和jar中的内容
    packagingOptions {
    exclude 'META-INF/**'
    exclude 'lib/arm64-v8a/libmediaplayer.so'
    }
    -
  2. -
  3. pickFirst:匹配到多个相同文件,只提取第一个。只作用于APK,不能过滤aar和jar中的文件
    packagingOptions {
    pickFirst "lib/armeabi-v7a/libaaa.so"
    pickFirst "lib/armeabi-v7a/libbbb.so"
    }
    -
  4. -
  5. doNotStrip:可以设置某些动态库不被优化压缩
    packagingOptions{
    doNotStrip "*/armeabi/*.so"
    doNotStrip "*/armeabi-v7a/*.so"
    }
    -
  6. -
  7. merge:将匹配的文件都添加到APK中,和pickFirst有些相反,会合并所有文件
    packagingOptions {
    merge '**/LICENSE.txt'
    merge '**/NOTICE.txt'
    }
    -
  8. -
-

统一版本

-

应用由多个 module 构成,而不同地方引用的包,需要做到全局的统一时,可以创建一个 xxx.gradle 的文件(这里的 xxx,自行取一个表明含义的内容即可),然后在使用的地方时,统一调用定义的版本即可,使用步骤如下

-
    -
  1. 创建 xxx.gradle 文件(一般放在项目的根目录,和顶级 build.gradle 文件在同一层级),并添加如下内容,可根据自身需要调整
    ext {

    android = [
    compileSdkVersion: 29,
    buildToolsVersion: "29.0.2",
    minSdkVersion : 19,
    targetSdkVersion : 29,
    versionCode : 2020010102,
    versionName : "0.1.0"
    ]

    version = [
    androidSupportSdkVersion: "29.0.0",
    retrofitSdkVersion : "2.6.3",
    okhttpSdkVersion : "4.3.0",
    dagger2SdkVersion : "2.22.1",
    glideSdkVersion : "4.9.0",
    butterknifeSdkVersion : "10.2.1",
    rxlifecycle2SdkVersion : "2.2.1",
    espressoSdkVersion : "3.0.2",
    canarySdkVersion : "1.5.4"
    ]

    // Android support 与 AndroidX support 对比
    // https://developer.android.google.cn/jetpack/androidx/migrate

    // support 库说明
    // https://developer.android.com/topic/libraries/support-library/features?hl=zh-cn
    dependencies = [
    // support
    "appcompat" : "androidx.appcompat:appcompat:1.1.0",
    "annotations" : "androidx.annotation:annotation:1.0.0",
    "cardview-v7" : "androidx.cardview:cardview:1.0.0",
    "constraint-layout" : "androidx.constraintlayout:constraintlayout:1.1.3",
    "swiperefreshlayout" : "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0",
    "material" : "com.google.android.material:material:1.1.0",
    "viewpager" : "androidx.viewpager:viewpager:1.0.0",
    "recyclerview" : "androidx.recyclerview:recyclerview:1.1.0",
    "vectordrawable" : "androidx.vectordrawable:vectordrawable:1.1.0",
    "support-v4" : "androidx.legacy:legacy-support-v4:1.0.0",

    // network
    "retrofit" : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}",
    "retrofit-converter-gson" : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}",
    "retrofit-converter-simplexml": "com.squareup.retrofit2:converter-simplexml:${version["retrofitSdkVersion"]}",
    "retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}",
    "okhttp3" : "com.squareup.okhttp3:okhttp:${version["okhttpSdkVersion"]}",
    "okhttp3-logging-interceptor" : "com.squareup.okhttp3:logging-interceptor:${version["okhttpSdkVersion"]}",
    "mockwebserver" : "com.squareup.okhttp3:mockwebserver:${version["okhttpSdkVersion"]}",
    "glide" : "com.github.bumptech.glide:glide:${version["glideSdkVersion"]}",
    // (annotationProcessor)
    "glide-compiler" : "com.github.bumptech.glide:compiler:${version["glideSdkVersion"]}",
    "glide-loader-okhttp3" : "com.github.bumptech.glide:okhttp3-integration:${version["glideSdkVersion"]}",

    // view
    "butterknife" : "com.jakewharton:butterknife:${version["butterknifeSdkVersion"]}",
    "butterknife-compiler" : "com.jakewharton:butterknife-compiler:${version["butterknifeSdkVersion"]}",
    "brvah" : "com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.49-androidx",
    "psid" : "com.oushangfeng:PinnedSectionItemDecoration:1.3.2-androidx",
    "material-dialogs" : "com.afollestad.material-dialogs:core:2.8.1",
    "material-input" : "com.afollestad.material-dialogs:input:2.8.1",
    "material-files" : "com.afollestad.material-dialogs:files:2.8.1",
    "material-color" : "com.afollestad.material-dialogs:color:2.8.1",
    "material-datetime" : "com.afollestad.material-dialogs:datetime:2.8.1",
    "pickerview" : "com.contrarywind:Android-PickerView:4.1.8",
    "photoview" : "com.github.chrisbanes.photoview:library:2.0.0",
    "lottie" : "com.airbnb.android:lottie:3.0.1",
    "badge-view" : "q.rorbin:badgeview:1.1.3",

    // rx2
    "rxandroid2" : "io.reactivex.rxjava2:rxandroid:2.1.1",
    "rxjava2" : "io.reactivex.rxjava2:rxjava:2.2.16",
    // https://github.com/VictorAlbertos/RxCache
    "rxcache2" : "com.github.VictorAlbertos.RxCache:runtime:1.8.3-2.x",
    // https://github.com/tbruyelle/RxPermissions
    "rxpermissions2" : "com.github.tbruyelle:rxpermissions:0.10.2",

    // tools(implementation)
    "dagger2" : "com.google.dagger:dagger:${version["dagger2SdkVersion"]}",
    "dagger2-android" : "com.google.dagger:dagger-android:${version["dagger2SdkVersion"]}",
    "dagger2-android-support" : "com.google.dagger:dagger-android-support:${version["dagger2SdkVersion"]}",
    "eventbus" : "org.greenrobot:eventbus:3.1.1",
    "gson" : "com.google.code.gson:gson:2.8.5",
    // https://projectlombok.org/setup/android
    "lombok" : "org.projectlombok:lombok:1.18.8",
    "multidex" : "com.android.support:multidex:1.0.3",
    "arouter-api" : "com.alibaba:arouter-api:1.4.1",
    "arouter-compiler" : "com.alibaba:arouter-compiler:1.2.2",
    //(annotationProcessor)
    "dagger2-compiler" : "com.google.dagger:dagger-compiler:${version["dagger2SdkVersion"]}",
    "dagger2-android-processor" : "com.google.dagger:dagger-android-processor:${version["dagger2SdkVersion"]}",

    // test
    "junit" : "junit:junit:4.12",
    "androidJUnitRunner" : "androidx.test.runner.AndroidJUnitRunner",
    "runner" : "androidx.test:runner:1.1.1",
    "espresso-core" : "androidx.test.espresso:espresso-core:3.2.0",
    "espresso-contrib" : "androidx.test.espresso:espresso-contrib:3.2.0",
    "espresso-intents" : "androidx.test.espresso:espresso-intents:3.3.0",
    "canary-debug" : "com.squareup.leakcanary:leakcanary-android:${version["canarySdkVersion"]}",
    "canary-release" : "com.squareup.leakcanary:leakcanary-android-no-op:${version["canarySdkVersion"]}",
    "umeng-analytics" : "com.umeng.analytics:analytics:6.0.1",

    // util
    // https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/README-CN.md
    "utilcode" : "com.blankj:utilcode:1.23.7",

    // help
    "logger" : "com.orhanobut:logger:2.2.0",
    // https://www.pgyer.com/doc/view/new_sdk_android_guide
    "pgy" : "com.pgyersdk:sdk:3.0.3",
    // SDK 包
    // https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613
    // https://jcenter.bintray.com/com/tencent/bugly/crashreport/
    "crashreport" : "com.tencent.bugly:crashreport:3.1.0",
    // 升级 SDK 包
    // https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613
    // https://jcenter.bintray.com/com/tencent/bugly/crashreport_upgrade/
    "bugly-crash-upgrade" : "com.tencent.bugly:crashreport_upgrade:1.4.2",
    // NDK 动态库
    // https://bugly.qq.com/docs/release-notes/release-android-ndk/?v=20180709165613
    // https://jcenter.bintray.com/com/tencent/bugly/nativecrashreport/
    "bugly-ndk" : "com.tencent.bugly:nativecrashreport:3.7.1",

    ]
    }
    -
  2. -
  3. 在顶级的 build.gradle 文件底部,表明添加对 xxx.gradle 的使用
    apply from: "xxx.gradle"
    -
  4. -
  5. 在 module 级别的 build.gradle 文件中,修改哪些固定写死的依赖版本
    // 之前固定的版本
    minSdkVersion 19
    // 修改通过 xxx.gradle 中定义的版本
    minSdkVersion rootProject.ext.android["minSdkVersion"]
    -
  6. -
-

参考

-
    -
  1. Android Gradle 插件版本说明
  2. -
  3. 配置构建
  4. -
  5. 配置构建变体
  6. -
-]]>
- - Gradle - - - Gradle - -
- - Gradle(三)SpringBoot 单工程 - /2020/12/16/gradle3/ - Gradle(一)基础 的文章中,我们已经对 Gradle 有了一定的认识,本篇来看一看在后端开发中使用 Gradle 构建 SpringBoot 项目的开发。通常有两种方式来构建项目,第一种:每个功能模块即是一个代码工程,用一个 Git 仓库来管理,每个模块只负责完成一件事情;第二种:整个系统的多个模块聚合在一个代码工程里面,也就是我们常说的多模块项目,本篇先来讲一讲单工程

- -

工程选择

-

对于单工程,和聚合工程的选择主要根据你所在项目团队的大小,项目分工,以及项目的复杂程度等来考虑。

-

单工程:适用于项目分工明确,项目庞大复杂,架构服务边界划分明确,配套的自动化等设施完善
-聚合工程:适用于项目人员不是很多,项目功能一般,需要一个人集中化管理等

-

环境

-
    -
  • OS:macOS 11.1
  • -
  • JDK:JDK1.8
  • -
  • Gradle:6.7.1-bin
  • -
  • IDE:IntelliJ IDEA Community 2020.3
  • -
  • SpringBoot:2.4.1
  • -
-

build.gradle

-
// 项目使用插件,可从 https://plugins.gradle.org 库中寻找合适的插件
plugins {
id 'org.springframework.boot' version '2.4.1'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}

// 这里和 maven类似,用于项目唯一坐标
group = 'com.example'
version = '0.0.1-SNAPSHOT'
// 项目兼容版本
sourceCompatibility = '1.8'

// 依赖第三方jar从哪个仓库去下载
repositories {
mavenCentral()
}

// 项目所需的第三方依赖
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

// 测试相关
test {
useJUnitPlatform()
}
-

一个 SpringBoot 项目基本的 build.gradle 文件由 plugins,项目坐标,repositories,dependencies,test 基础内容组成。关于 plugins 使用常见有两种方式,核心的依赖,是没有版本号,它和你使用的 Gradle 关联,你无需过多关系这些核心插件的依赖版本

-
// 旧方式
apply plugin: 'java'

// 新方式(推荐)
plubins {
id 'java'
}
-

settings.gradle

-

用于项目模块管理,由于这个单工程,这里只有一个模块

-
rootProject.name = 'demo'
-

多环境

-

可通过自定义 task 来出来

-
// prod
tasks.register("bootRunProd") {
group = "application"
description = "Runs the Spring Boot application with the prod profile"
doFirst {
tasks.bootRun.configure {
systemProperty("spring.profiles.active", "prod")
}
}
finalizedBy("bootRun")
}

// dev
tasks.register("bootRunDev") {
group = "application"
description = "Runs the Spring Boot application with the dev profile"
doFirst {
tasks.bootRun.configure {
systemProperty("spring.profiles.active", "dev")
}
}
finalizedBy("bootRun")
}
-

启动方式

-
    -
  • -

    方式一:图形化界面中,直接运行对应环境
    -

    -
  • -
  • -

    方式二:在命令行中,使用命令来运行对应环境,比如 gradlew bootRunDev
    -

    -
  • -
  • -

    方式三:当然你也可以在启动时指定你需要激活的环境

    -
    # 这里激活的 test 环境,把 ${jar_name} 参数换成对应启动的应用文件
    java -jar ${jar_name} --spring.profiles.active=test
    -
  • -
-

排除依赖

-
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
-

打包

-

打包时需要,注意我们的 SpringBoot 应用它本质上是一个 bootJar(Fatjar) 应用,因此需要将应用打成一个 bootJar(Fatjar)。而对于什么是 bootJar 和 jar 的区别,可以查看之前在 SpringBoot(二) 启动分析JarLauncher 文章中对于 jar 规范 说明

-

打包方式

-
    -
  • -

    方式一:图形化操作

    -
  • -
  • -

    方式二:命名执行

    -
    # 在项目的根目录执行,Windows 使用:gradlew;Linux/macOS:./gradlew
    # 当然如果那你已安装且配置好 gradle 的环境,你可以直接使用 gradle 代替 ./gradlew 的相关命令
    gradlew bootJar
    -
  • -
-

发布

-

参考

-
    -
  1. SpringBoot+gradle 构建多模块项目
  2. -
  3. IDEA 2020.2 + Gradle 6.6.1 + Spring Boot 2.3.4 创建多模块项目
  4. -
  5. Spring-boot 2.3.x 源码基于Gradle编译
  6. -
  7. 用 Gradle 构建 Spring Boot 项目
  8. -
  9. 使用 Gradle 构建 springboot 多模块项目,并混合groovy开发
  10. -
  11. Spring Boot Gradle Plugin Reference Guide
  12. -
-]]>
- - Gradle - - - Gradle - -
- - Hexo Blog 高级指南 - /2020/11/20/hexo-advanced/ - NexTHexo 非常受欢迎的博客主题,方便简洁,但却不简单的功能,你可以在提供的强大功能基础上进行扩展或者自定义,来满足你的个性化需求。本篇文章主要是对应 NexT 提供的一些高级功能的使用,作为一个持续更新的文章吧,记录自己 SX 操作,当然也是我平时在使用 NexT 时遇到的一些问题的记录。好了废话不多说了,我们直接进入正题

- -

博客升级

-

每次对于 NexT 的升级或多或少都会遇到些问题,这次也不例外,首先是对于不同版本的管理,由于一些历史原因有三个组织仓库分别对应不同的版本域,升级是需要注意下,本次我是从 7.8.0 版本升级到 8.0.x 版本,以后跟随官方,每月更新 NexT

-

npm 改成 yarn(可选)

-
-

yarn 的安装,请自行根据你的系统去安装,我这里 macOS 使用命令即可 brew install yarn

-
-
    -
  1. 删除根目录的 package-lock.json,并在根目录执行 hexo clean && rm -rf node_modules/
  2. -
  3. 根目录下执行 yarn install
  4. -
-

更改 NexT 主题仓库

-
    -
  1. 删除当前主题,在根目录下执行 rm -rf themes/
  2. -
  3. 安装新的主题, -
      -
    • 方案一:在根目录下执行命令添加主题
    • -
    -
    git clone https://github.com/next-theme/hexo-theme-next themes/next
    -
      -
    • 方案二:通过 yarn 来管理主题
    • -
    -
    yarn add hexo-theme-next
    -
  4. -
-

修改配置

-

之前为了使主题更新不受影响,在项目的根目录 source/_data 路径下有一个 next.yml 文件来进行对 NexT 的自定义设置,那么在 8.0 版本开始,在项目根目录 _config.{theme}.yml 文件来代替之前在 source/_data 路径下的 next.yml 文件

-

问题

-

node --trace-warnings

-
异常信息
-

由于 NexT 需要 Hexo5.0+,在升级到 NexT 8.0.x 版本警告信息如下

-
(node:17336) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
(node:17336) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:17336) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
-
原因分析
-

是由于 Hexo 项目嵌套依赖了 stylus 包,而对于 0.54.5 版本在 Node 14+ 版本存在问题,比如这里 hexo-renderer-stylus 包的依赖

-
……
│ │
├─┬ hexo-renderer-stylus@2.0.1
│ ├─┬ nib@1.1.2
│ │ └─┬ stylus@0.54.5
│ │ │
……
-
解决方法
-
    -
  1. 可以降低你的 Node 版本到 12 版本
    brew uninstall node
    brew install node@12
    brew link --overwrite --force node@12
    -
  2. -
  3. 推荐,更改替换 stylus 版本,在你的 package.json 文件中,添加如下配置
    "resolutions": {
    "stylus": "^0.54.8"
    }
    -
  4. -
-
总结
-

🌀 pull-2538
-🐞 issues-2534
-🛠 solve-Accessing non-existent property

-

hexo-douban

-

之前用了 hexo-douban 插件来进行对 books 和 movies 进行管理,在升级到 Node 14+版本上,当前的插件也停止工作了,异常日志如下

-
INFO  0 books have been loaded in 1130 ms, because you are offline or your network is bad
INFO 0 movies have been loaded in 1329 ms, because you are offline or your network is bad
INFO 0 games have been loaded in 1004 ms, because you are offline or your network is bad
-

作者在🐞 issues-2534 做了回复,暂时没有替代方案,故在新版中,我停止了 hexo-douban 插件的使用,挖个坑,等自己有时间或者有人修复此问题再或者有替代插件后再重新启用

-
    -
  1. 移除 hexo-douban 插件
    # yarn
    yarn remove hexo-douban
    # npm
    npm uninstall hexo-douban
    -
  2. -
  3. 移除 _config.yml 配置文件中,douban 的相关的配置
  4. -
  5. 移除 _config.{theme}.yml 配置文件中,menu 配置的站点入口设置
  6. -
-

博客评论

-

在 NexT version 8.1.0 版本,由于安全问题,Valine被移除暂时我并未迁移 Valine 的评论

-

博客已启用 utterances 评论支持,配置也比较简单,如下

-
utterances:
enable: true
repo: BladeCode/BladeCode.github.io # Github repository name
# Available values: pathname | url | title | og:title
issue_term: title
# Available values: github-light | github-dark | preferred-color-scheme | github-dark-orange | icy-dark | dark-blue | photon-dark | boxy-light
theme: github-light
-

文章加密

-

对于 NexT 的文章,有时需要进行加密访问,那么该怎么去处理呢,其实这一点在 NexT 的生态里已经有了这样的插件,我们可以直接在使用在我们的 NexT 里面,只需要简单的配置

-
# npm 
npm i hexo-blog-encrypt --save
# yarn
yarn add hexo-blog-encrypt
-

加密优先级:文章信息头 > 按标签加密

-

站点配置(_config.yml)

-

简单配置

-
# 文章密码访问 hexo-blog-encrypt
encrypt:
enable: true
-

更多配置

-

可以对一类(标签)来进行统一的密码设置

-
# 文章密码访问 hexo-blog-encrypt
encrypt:
abstract: 有东西被加密了, 请输入密码查看.
message: 您好, 这里需要密码.
tags:
- {name: tagNameA, password: 密码A}
- {name: tagNameB, password: 密码B}
template: <div id="hexo-blog-encrypt" data-wpm="{{hbeWrongPassMessage}}" data-whm="{{hbeWrongHashMessage}}"><div class="hbe-input-container"><input type="password" id="hbePass" placeholder="{{hbeMessage}}" /><label>{{hbeMessage}}</label><div class="bottom-line"></div></div><script id="hbeData" type="hbeData" data-hmacdigest="{{hbeHmacDigest}}">{{hbeEncryptedData}}</script></div>
wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.
-

单文章配置

-

在你需要加密的文章前面,根据需要添加对应的参数,这里仅是一个示例

-
---
title: Hello World
tags:
- 加密文章tag
date: 2020-11-20 18:18:18
password: helloworld
abstract: 该文章已加密, 请输入密码查看。
message: 该文章已加密, 请输入密码查看。
wrong_pass_message: 密码不正确,请重新输入!
wrong_hash_message: 文章不能被校验, 不过您还是能看看解密后的内容!
---
-

各参数说明

-
    -
  • password:文章密码
  • -
  • abstract:文章摘要,会显示在博客的列表页
  • -
  • message:文章查看时,密码输入框上面的描述性文字
  • -
  • wrong_pass_message:校验失败提示
  • -
  • wrong_hash_message:hash 验证失败
  • -
-

多语言

-

对于多语言,根据自身需要添加,默认,修改博客项目根目录 _connfig.yml 文件 language 属性即可

-
    -
  1. 对于单语言:language: xxx(具体语言可查看下方的官方说明)
  2. -
  3. 对于多语言: -
      -
    • 语言添加
      language:
      - zh-CN
      - en
      -
    • -
    • 更改语言切换,_config.{theme}.yml 文件,language_switcher设置为 true
    • -
    -
  4. -
  5. 字段定义,如果一些字段的翻译不是你想要的,你可以自行修改 -
      -
    • 在根目录的 source/_data 文件夹下,创建 languages.yml 文件
    • -
    • 在文件中,修改对应语言的字段
      zh-CN:
      # items
      post:
      copyright:
      # the translation you perfer
      author: 本文博主
      en:
      menu:
      schedule: Calendar
      -
    • -
    -
  6. -
-
-

多语言配置

-
-

GitHub Action

-

Hexo PWA

-
-

由于暂未支持 Hexo5.0+版本,先占坑

-
-

参考

-
    -
  • 更新说明及常见问题
  • -
  • 将 Hexo 升级到 v5.0.0
  • -
  • 用 GitHub Actions 来自动部署 Hexo
  • -
  • Hexo博客部署PWA
  • -
  • 博客完美支持 PWA
  • -
  • 三步,让 Hexo 轻松支持 PWA
  • -
  • Pwabuilder
  • -
  • Hexo 相关问题和优化
  • -
-]]>
- - Hexo - - - Build - -
- - Hexo Blog 搭建 - /2018/03/25/hexo-blog/ - 之前一直纠结用Jekyll还是Hexo来搭建GitHub Page博客,原本一直想搭建一个Material Design主题风格,从Hexo Themes中寻找到一款不错的主题,indigo是一款支持IE10+,评论,目录导航,分享等功能的轻量Blog主题。

-

简单的修改了该主题之后,本地预览都没有什么问题,但是部署到Github上,样式什么的都无法加载,应该是我的操作姿势不对吧,调整了半天没有解决,烦躁中找到之前star的另一款很受欢迎的Next主题。

-

既然自己修改的无法正常部署预览,那就用别人写好的吧,刚好赶上Next新版本V6.0系列的推出,那就不废话,直接开干

- -

材料准备

-
    -
  • Node LTS
  • -
  • Git
  • -
  • Hexo
  • -
  • Next
  • -
-

安装

-

Node,Git的安装过程略

-

Hexo

-
    -
  1. Hexo 安装
    $ npm install hexo-cli -g
    -
  2. -
  3. 初始化
    $ hexo init <your blog name>
    -
  4. -
  5. 安装依赖包
    $ cd <you blog name>
    $ npm install
    -
  6. -
  7. 启动服务预览
    $ hexo serve
    -
  8. -
-

Next

-
    -
  1. -

    安装Next 主题

    -
    $ git clone https://github.com/theme-next/hexo-theme-next themes/next
    -
    -

    当前操作在 blog的根目录下执行

    -
    -
  2. -
  3. -

    修改Blog 配置
    -you blog name 根目录 _config.yml

    -
      -
    • theme: 由原来默认landscape更改位next(大约:76行)
    • -
    • 其他配置项,根据自己的需求进行更改,我这里更改了title,subtitle,author,language,url配置,其中language如果没有修改,默认为英文语言,在V6.0系列由原来zh-Hans更新为zh-CN
    • -
    • 添加部署到Github配置
    • -
    -
    deploy:
    type: git
    repo: https://github.com/BladeCode/BladeCode.github.io.git # 用户名仓库
    branch: master # 用户名仓库的分支应该指定master,master分支也可以不用写
    -
  4. -
  5. -

    修改Theme 配置
    -路径:you blog name/Themes/next/_config.yml
    -这里不罗嗦了,其配置可参考hexo-theme-next项目README文件

    -
  6. -
-

部署

-

上面已经配置好了部署的目标仓库,那么这里直接使用Hexo提供的部署命令即可

-
$ hexo d
-

相关命令介绍等,请查看官方文档说明

-

部署完成后,可以直接访问 http://`you blog name`/github.io

-

自定义域名

-

虽然现在 blog 可以使用 Github 提供的项目二级域名来访问,为了个性化以及方便等,配置自己的域名

-
    -
  1. -

    登录域名所属的管理网站(这里以阿里云域名服务为例)
    -gitpages-domain-manger

    -
  2. -
  3. -

    添加解析

    -
    $ # 解析一
    记录类型:CNAME
    主机记录:www
    记录值:bladecode.github.io
    解析路线:default

    $ # 解析二
    记录类型:A
    主机记录:@
    记录值:192.30.252.153
    解析路线:default

    $ # 解析三
    记录类型:A
    主机记录:@
    记录值:192.30.252.154
    解析路线:default
    -
    -

    192.30.252.153是GitHub的地址,你也可以ping你的 http://xxxx.github.io 的ip地址,填入进去

    -
    -
  4. -
  5. -

    修改Github上项目的domain设置
    -gitpages-domain-custom

    -
  6. -
  7. -

    添加CNAME文件
    -保存路径:you blog name/source
    -新增文件:CNAME 文件 (格式要求:保存成所有文件而不是txt文件)
    -CNAME 文件内容:incoder.org

    -
  8. -
-
-

如果带有www,那么以后访问的时候必须带有www完整的域名才可以访问,但如果不带有www,以后访问的时候带不带www都可以访问。所以建议,不要带有www

-
-

Https开启

-

开启Https 需要借助Cloudflare,关于Cloudflare的介绍等不在这里展开

-
    -
  1. 注册账号
  2. -
  3. Add website
    -site
  4. -
  5. Querying your DNS
    -query
  6. -
  7. Select Plan
    -plan
  8. -
  9. 域名解析记录获取
    -continue
  10. -
  11. DNS 对比,并修改Cloudflare提供的DNS来解析
    -change
  12. -
  13. 域名管理后台,修改DNS
    -dns -
    -

    阿里云服务相关域名DNS修改帮助文档

    -
    -
  14. -
  15. 成功激活
    -active
  16. -
  17. SSL证书申请提醒
    -cer
  18. -
  19. 添加强制HTTPS规则
    -rule
  20. -
  21. 规则制定
    -deploy
  22. -
-

好了剩下的就是等证书颁发,可能要等上一些时间,具体每个人不尽相同,这里就不多做解释了。

-

Let’s all,本次的Hexo的相关初级教程就到这里

-]]>
- - Hexo - - - Build - -
- - Hexo Blog 迭代 - /2018/05/02/hexo-iterative/ - 最初博客通过Cloudflare反向代理进行HTTPS解析,放完五一假期,Github官方开始支持自定义域名的HTTPS解析,在使用Cloudflare期间,经常性的521等问题烦恼,这次也可以名正言顺的弃用CloudFlare

-

本次迭代内容

-
    -
  • 弃用Cloudflare
  • -
  • 自动化部署
  • -
  • 常用设置
  • -
  • 常用插件安装
  • -
- -

弃用Cloudflare

-
    -
  1. 关闭Cloudflare中设置Page Rules
  2. -
  3. 删除Cloudflare的DNS记录
  4. -
  5. 还原域名配置中的DNS解析
  6. -
  7. 添加Github提供的IP解析
  8. -
-

官方自定义域名设置

-

自动化部署

-
-

Github Pages是Github 提供一个渲染静态的Web页面服务

-
-
    -
  • {username}.github.io仓库默认master分支
  • -
  • 其他项目仓库,默认gh-pages分支
  • -
  • 官方说明文档
  • -
-

因此{username}.github.io仓库,dev分支用来存储网站的源码,master分支存放生成的静态文件,这样一个仓库就可以管理整个项目。每次push新的功能,然而每次都需要先pushdev分支,然后生成静态文件,再pushmaster分支,这种重复性的操作,实在太不优雅,所以采用Travis CI进行自动化部署

-

接着Github支持自定义域名开启HTTPS的好消息,Travis CI (https://travis-ci.com) 也支持开源项目啦

-
-

Travis CI 区别

-
-
    -
  • Travis-CI(https://travis-ci.org) :GitHub公开项目
  • -
  • Travis-CI(https://travis-ci.com) :私有付费项目2018.05.02也开始支持开源项目
  • -
-

GitHub Services are being deprecated,因此本节的自动化部署就开启Travis CI (https://travis-ci.com) 集成方案

-

准备

-
    -
  1. 使用GitHub账号登录Travis-CI,并确认接受访问
  2. -
  3. 同步了GitHub存储库,转到您的配置文件页面并启用您想要构建的存储库
  4. -
  5. 添加 .travis.yml 文件到构建部署项目的根目录下
  6. -
-

Hexo 自动部署

-

部署流程
-部署流程

-

Hexo 部署脚本示例

-
# 设置语言
language: node_js
# 设置相应的版本
node_js:
- '12.16.3'
# - lts/*
# 可以减少travis构建时间
cache:
directories:
- node_modules
before_install:
# - npm config set bin-links false
# - npm install -g hexo
- npm install -g hexo-cli
# 安装hexo及插件
install:
- npm install
before_script:
- npm install -g mocha
- git clone --branch master https://github.com/BladeCode/BladeCode.github.io.git public
script:
# 清除
- hexo cl
# 生成
- hexo g
after_script:
- cd ./public
- git init
# 修改成自己的github用户名
- git config user.name "BladeCode"
# 修改成自己的GitHub邮箱
- git config user.email "Jerry.x@outlook.com"
- git add .
- git commit -m "update by Travis-CI on `date '+%Y-%m-%d %H:%M:%S'`"
# GH_token就是在travis中设置的token
- git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:master
branches:
only:
# 只监测这个分支,一有动静就开始构建
- dev
env:
global:
# 设置仓库地址
- GH_REF: github.com/BladeCode/BladeCode.github.io.git

-

常用设置

-

NexT 配置使用手册
-NexT 配置使用手册

-

NexT主题更新

-

官方说明

-

常用插件安装

-
    -
  • 文章字符统计 hexo-symbols-count-time
  • -
  • 修复LeanCloud访客计数器中的严重安全漏洞 hexo-leancloud-counter-security
  • -
  • 图片灯箱 theme-next-fancybox3
  • -
  • 本地检索 hexo-generator-searchdb
  • -
  • 注脚 hexo-renderer-markdown-it-plus
  • -
  • 文章加密 hexo-blog-encrypt
  • -
-

其他

-

图床选择

-
    -
  • 个人网站中的静态文件云存储选择
  • -
  • 嗯,图片就交给它了
  • -
  • NexT主题无法备份解决方式
  • -
-]]>
- - Hexo - - - Build - -
- - Hugo 初体验 - /2018/07/11/hugo/ - 个人博客使用 Hexo 搭建,使用效果很不错,RootCluster 组织主要存放自己新技术的学习和一些Demo实验。该组织同样也可以使用Github pages服务,因此也需要给RootCluster构建一个静态页面,可用直观清晰的看自己的项目,虽然之前已使用Hexo构建,为了了解其他的静态页面构建,所以这次选择了 Hugo

-

Hugo 是世界上最快的静态网站引擎。它是用 Go(aka Golang)编写的,由 bepspf13朋友开发

- -

材料准备

-
    -
  • SystemOS:Windows 10
  • -
  • Chocolatey:Windows的包管理器
  • -
  • Hugo
  • -
-

安装

-

Chocolatey安装

-

如果已安装,跳过该步骤

-
    -
  • 使用 PowerShell.exe
    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
    -
  • -
  • 使用 cmd.exe
    @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
    -
  • -
-

以上两种方式,选择其一即可

-

PowerShell.exe 演示

-

hugo安装

-
choco install hugo -confirm
-

初始化Hugo

-
    -
  • -

    初始化hugo模板

    -
    hugo new site project_name
    -
  • -
  • -

    进入项目并启动项目

    -
    cd project_name && hugo serve
    -

    hugo_init

    -
  • -
  • -

    主题安装

    -

    这里选择Elate主题作为组织的网站

    -

    -
  • -
-

GitHub Action 部署

-
    -
  1. 新生成部署 key
  2. -
-
# 1. 进入本地电脑的 .ssh 文件夹 
cd .ssh/
# 2. 生成部署 key
ssh-keygen -t rsa -b 4096 -C "Jerry.x@outlook.com" -f id_rsa_deploy -N ""
-
    -
  1. 添加部署 key 到项目仓库设置中
  2. -
-
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the action will run.
on:
# Triggers the workflow on push or pull request events but only for the master branch
push:
branches: master
pull_request:
branches: dev

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
submodules: true
fetch-depth: 0
# Runs a single command using the runners shell
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2.3.1
with:
hugo-version: '0.61.0'
# Runs a set of commands using the runners shell
- name: Build
run: hugo --minify

- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.ACTIONS_DEPLOY_KEY }}
publish_dir: ./public
commit_message: ${{ github.event.head_commit.message }}
-

Travis

-
language: go

go:
- master # 使用最新版本

# Specify which branches to build using a safelist
# 分支白名单限制: 只有hugo分支的提交才会触发构建
branches:
only:
- dev

install:
# 安装最新的hugo
- go get -v github.com/gohugoio/hugo

script:
# 运行hugo命令
- hugo

deploy:
provider: pages # 重要,指定这是一份github pages的部署配置
skip-cleanup: true # 重要,不能省略
local-dir: public # 静态站点文件所在目录
target-branch: master # 要将静态站点文件发布到哪个分支
github-token: $GITHUB_TOKEN # 重要,$GITHUB_TOKEN是变量,需要在GitHub上申请、再到配置到Travis
keep-history: true # 是否保持target-branch分支的提交记录
on:
branch: dev # 博客源码的分支
-

参考

-
    -
  1. Hugo Documentation
  2. -
  3. Hugo setup
  4. -
-]]>
- - Hugo - - - Build - -
- - IDEA 多模块项目 - /2019/01/10/idea-multi-module/ - Jetbrains系列中IDEA是现如今公认最好用,最强大的Java开发工具,不接受任何反驳,本篇介绍macOS上使用 IDEA 创建 SpringBoot 多模块项目

-

准备工作

-
    -
  • 系统环境:macOS 10.14.2
  • -
  • 应用工具:IDEAMaven
  • -
-
-

这里不再介绍基本软件的安装及配置

-
- -

多模块项目

-

一般简单的项目,按照如下项目结构进行构建,可根据也无需要自行调整

-
rc-springboot-docker
├── boot-api # 项目对应用服务间提供api的接口,同时也管理项目常量、REST返回组装实体类等
├── boot-common # 项目公共基础包(可丢弃)
├── boot-core # 项目业务操作,server dao层
├── boot-web # 项目后端Web管理
├── boot-rest # 项目业务控制层,给客户端提供rest接口
└── README.md
-
    -
  • boot-api:是一个maven module
  • -
  • boot-common:是一个maven module
  • -
  • boot-core:是一个maven module
  • -
  • boot-web:是一个springboot module
  • -
  • boot-rest:是一个springboot module
  • -
-

构建

-

Parent Project

-

顾名思义,这是项目的外壳,一个标准的empty maven project,当然你要可以使用gradle来作为项目的构建工具,可根据自身需要自行选择,这里采用maven方式演示

-
    -
  • -

    Create Project
    -idea-new-project

    -
  • -
  • -

    设置项目groupId和artifactId等信息
    -idea-new-setting

    -
  • -
  • -

    设置项目名称及项目存储位置
    -idea-new-path

    -
  • -
  • -

    删除项目src目录,使项目成为名副其实的空项目
    -idea-delete-src

    -
  • -
  • -

    新增忽略文件
    -idea-new-ignore
    -新增忽略文件的目的:

    -
      -
    1. 忽略项目中不需要进行版本追踪的文件
    2. -
    3. 隐藏忽略文件
    4. -
    -
  • -
  • -

    选择maven项目模板忽略文件
    -idea-select-maven

    -
  • -
  • -

    修改忽略文件及隐藏忽略文件
    -idea-ignore-settings

    -
    # IntelliJ project files
    .DS_Store
    .idea/
    *.iml
    out
    gen

    # eclipse
    *.classpath
    *.project
    *.springBeans
    -
    -

    关于ignore文件的写法,可以参考.gitignore 基础知识

    -
    -
  • -
-

Module Project

-

在module中有两类,一类是maven项目,还有一类是需要启动的springboot项目

-

maven module project

-
    -
  • 创建maven module
    -idea-module-maven
  • -
  • 设置maven module artifactId等信息
    -idea-module-maven-artifact
  • -
  • 设置maven module 名称及存储位置
    -idea-module-maven-name
  • -
-

springboot module project

-
    -
  • 创建springboot module
    -idea-new-module-springboot
  • -
  • 设置springboot module 信息
    -idea-module-metadata
  • -
  • 选择核心组件
    -idea-module-springboot-core
  • -
  • 设置springboot module 名称及存储位置
    -idea-module-springboot-name
  • -
-

Modify Config

-

Modify parent pom

-]]>
- - IDEA - - - SpringBoot - Init - -
- - 开发小技巧 - /2019/01/02/idea-skill/ - Android Studio 是Google基于JetBrains的 IntelliJ IDEA 所定制开发的 Android 开发 IDE。因此这里的设置适用于 JetBrains 公司系列的开发工具,同样也适用于 Android Studio,这是一篇持续更新的文章,在平时的使用过程中一些习惯性的模板化的一些设置,可以减少我们一些重复性的操作,进而提高开发效率。

- -

设置

-

快捷键:

-
    -
  • Windows:Ctrl+Alt+S
  • -
  • macOS:+,
  • -
-

样式

-

约束提示/空格及缩进

-
    -
  • 描述: -
      -
    • 为了约束编写的代码过长而不换行,在代码编辑面板右侧右侧有个条竖线进行约束和警示,当然你可以关闭
    • -
    • 为了工整的显示代码的空格和换行是否正确,可以开启显示空格和缩进等样式
    • -
    -
  • -
-

idea-skill-line

-

窗口打开全部展示

-
    -
  • 描述:为了在编辑器中展示全部打开的文件(不限制在同一行)
    -idea-open-windows-limit
  • -
-

颜色

-

局部变量

-
    -
  • 描述:为了直观的区分出全局变量和局部变量,而不需要仔细阅读代码
    -idea-skill-local
  • -
-

控制台日志

-
    -
  • 描述:为了直观在控制台上显示不同级别日志
    -idea-logcat-color
  • -
-

颜色推荐:

-
    -
  • Assert:AA66CC
  • -
  • Debug:33B5E5
  • -
  • Error:FF6B68
  • -
  • Info:99CC00
  • -
  • Verbose:BBBBBB
  • -
  • Warning:FFBB33
  • -
-

其他

-

Toolbar添加设置按钮

-
    -
  • 描述:在不方便使用快捷键打开设置时,原本的操作是:File–>Settings Repository…,因此调整到状态栏上
    -idea-skill-settings
  • -
-

不区分大小写

-
    -
  • 描述:在编码过程中,通常一些智能提示需要根据输入的支付来提示,而大小写不同对应的提示也不完全一致,因此取消智能提示对大小写字符的要求
    -idea-skill-case
  • -
-

自动导包

-
    -
  • 描述:在编码过程中,一些无用或者需要引入的包,可设置成自动的方式,当无法自动导入或移除无用包时,再手动的去选择处理
    -idea-skill-auto
  • -
-

字段默认前缀

-
    -
  • 描述:为了让代码规范,我们会对变量前面设置默认前缀,那么 idea 也是支持
    -idea-field-prefix
  • -
-

常用技巧

-

编码技巧

-

调试技巧

-

参考

-
    -
  • IntelliJ-IDEA-Tutorial
  • -
-]]>
- - DevTool - - - Android Studio - JetBrains - -
- - JetBrains 系列激活 - /2020/02/27/idea-welfare/ -

- -

位于捷克的布拉格的JetBrains公司你可能不是很熟悉,但在开发的江湖中你一定听过他的名号,“Java 开发最好用 IDE,没有之一”,产品很好,但价格也不便宜。虽然有学生优惠,但对于非学生用户来说也是不小的开支。如果你想支持正版,官方还提供了一个开源项目渠道,你可以用你的开源项目进行申请,申请通过可以获得一年JetBrains全家桶的使用权限,这么好的福利当然不能错过。

-

我们先看看正常个人订阅价格图,IDEA:$149.00/1st year,全家桶产品:$249.00/1st year
-idea-price
-如果你工资比较高,建议还是支持一下,废话不多说,我们一起来看看怎么通过开源项目申请使用产品

-

申请地址:https://www.jetbrains.com/shop/eform/opensource?product=ALL

-

按照实际情况填写,大概一周左右,申请通过,JetBrains官方会发送邮件给你,你只需要按照邮件内的文件继续按照步骤进行操作,就可以获得 1 年的使用权限

-]]>
- - DevTool - - - JetBrains - -
- - 2020 年秋季面试经历 - /2020/11/15/interview-2020/ - - -
-
- - -
-
- -]]>
- - Summary - - - Interview - Summary - -
- - Linux 常用应用安装 - /2018/05/15/linux-build/ - 作为 Android 开发者,目标主要是在客户端,平时也就是和服务端对接数据接口,很少直接干到服务端的 Linux 机器,随着这波推动团队技术平台基础开发工具模块的完善,拿到了一台 Linux 机器,重新构建移动端的测试服务器。

-

该机器主要功能:

-
    -
  1. 提供移动端服务 Api 接口
  2. -
  3. 提供移动端通讯录管理授权服务
  4. -
  5. 提供企业微信通讯录同步服务
  6. -
  7. 管理移动端服务器 Api 接口文档
  8. -
- -

也是第一次正式的从头开始安装所需软件及应用部署,虽然这些工作可以完全找运维去处理,难得这样的机会从头开始去熟悉 Linux。

-

安卓,是一个基于Linux内核的开放源代码移动操作系统,因此多了解 Linux 是一件双赢的事情,基于当前机器需要提供的服务,安装部署需要的软件应用

-
废话不多说,上来就是干
-

和 Windows 一样不同的系统,安装的软件也是有区别的,而且 Linux 的系统众多,因此需要先查看系统的版本及相关信息,然后再下载对应系统版本的应用进行安装

-

查看 Linux 发行版的名称及其版本号

-

查看内核

-
    -
  1. cat /proc/version
    [dc2-user@10-255-0-191 ~]$ cat /proc/version
    Linux version 3.10.0-957.27.2.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Mon Jul 29 17:46:05 UTC 2019
    -
  2. -
  3. uname -a
    [dc2-user@10-255-0-191 ~]$ uname -a
    Linux 10-255-0-191 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
    -
  4. -
-

查看 Linux 系统版本

-
    -
  1. lsb_release -a:列出所有版本信息
    [dc2-user@10-255-0-191 ~]$ lsb_release -a
    LSB Version: :core-4.1-amd64:core-4.1-noarch
    Distributor ID: CentOS
    Description: CentOS Linux release 7.6.1810 (Core)
    Release: 7.6.1810
    Codename: Core
    -
  2. -
  3. cat /etc/redhat-release:只适合 Redhat 系的 Linux
    [dc2-user@10-255-0-191 ~]$ cat /etc/redhat-release
    CentOS Linux release 7.6.1810 (Core)
    -
  4. -
  5. cat /etc/issue:此命令也适用于所有的 Linux 发行版
    [root@localhost ~]# cat /etc/issue
    CentOS release 6.7 (Final)
    Kernel \r on an \m
    -
  6. -
-

Java

-

官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.rpm,.gz两种格式安装包

-
-
# 1. 下载安装包
# 拷贝安装包到需要安装的服务器
# 2. 解压并安装
# .rpm 格式安装(jdk-xxx.rpm更换成对应的文件名)
sudo rpm -ivh jdk-xxx.rpm
# .gz 格式安装(解压到指定目录,常存放 /usr/java/ 路径)
tar zxvf jdk-xxx.tar.gz -C /usr/java/
# 3. 设置环境变量
vim /etc/profile
# 指定 JDK 的配置信息(修改这里路径,指向 jdk 安装路径)
JAVA_HOME=/usr/java/jdk1.8.0_172
PATH=$JAVA_HOME/bin:$PATH
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME PATH CLASSPATH
# 4. 编译配置文件,使修改生效
source /etc/profile
# 5. 验证 jdk 是否安装成功
java –version
-

Tomcat

-

官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.zip,.gz两种格式安装包,Linux服务器下载Core类即可

-
-
# 1. 下载安装文件
wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-9/v9.0.8/bin/apache-tomcat-9.0.8.tar.gz
# 2. 解压安装文件(解压到指定目录,常存放 /usr/tomcat/ 路径)
tar -zxvf apache-tomcat-9.0.8.tar.gz -C /usr/tomcat/
# 3. 启动 tomcat
cd /usr/local/tomcat/bin
./startup.sh
# 4. 关闭 tomcat
./shutdown.sh
-

配置Web管理账号

-
    -
  • 修改文件 conf/tomcat-users.xml,在元素中添加帐号密码,需要指定角色
    vim /usr/local/tomcat/conf/tomcat-users.xml
    # <tomcat-users>
    # <user name="admin" password="admin" roles="admin-gui,manager-gui" />
    # </tomcat-users>
    -
  • -
-

配置端口

-
    -
  • 可以修改 conf 目录下的文件 server.xml,修改 Connector 元素(Tomcat 的默认端口是 8080),需要重新启动 Tomcat 服务生效
    vim /usr/local/tomcat/conf/server.xml
    # <Connector port="9999" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
    -
  • -
-

应用部署

-
    -
  • 放置需部署包到容器中 webapps 路径
    cd /usr/local/tomcat/webapps
    -
  • -
  • 启动服务
    cd /usr/local/tomcat/bin
    ./startup.sh
    -
  • -
-

Maven

-

官方网站,选择需要的版本下载

-
    -
  • 官方 Maven: https://maven.apache.org/
  • -
  • Maven 下载地址: https://maven.apache.org/download.cgi
  • -
  • Maven 历史版本: https://archive.apache.org/dist/maven/maven-3/
  • -
-

安装

-
# 下载文件 Maven 文件
wget https://mirrors.bfsu.edu.cn/apache/maven/maven-3/3.8.1/binaries/apache-maven-3.8.1-bin.tar.gz
# 解压 Maven 文件
tar -xvf apache-maven-3.8.1-bin.tar.gz
# 移动 Maven 文件到 /usr/local/ 路径
sudo mv -f apache-maven-3.8.1 /usr/local/
-

配置

-
# 编辑环境配置
vim /etc/profile
# 添加如下环境变量,文件末尾添加如下代码
export MAVEN_HOME=/usr/local/apache-maven-3.8.1
export PATH=${PATH}:${MAVEN_HOME}/bin
# 使环境变量生效
source /etc/profile
-

验证

-
mvn -v
-

如果需要更改 Maven 的镜像源,可参考 专治各种网络不服 文章

-

Apache

-

一般系统中已经包含 Apache 应用
-官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.bz2,.gz两种格式安装包

-
-

安装

-

查看系统中是否已包含 httpd 应用

-
rpm -qa | grep httpd
# 或
yum list | grep httpd
-
    -
  • -

    方式一

    -
    # 1. 下载需要的版本文件
    wget http://apache.claz.org//httpd/httpd-2.4.33.tar.gz
    # 2. 解压安装文件(解压到指定目录,常存放 /usr/local/httpd/ 路径)
    tar -zxvf httpd-2.4.33.tar.gz -C /usr/local/httpd/
    -
  • -
  • -

    方式二(推荐)

    -
    # 1.下载安装 httpd
    yum install httpd
    -
  • -
-

卸载

-
yum erase httpd.x86_64
# 或
rpm -e httpd.x86_64
-

常用命令

-
# 查看服务运行状态
systemctl status httpd.service
# 启动 apache 服务
systemctl start httpd.service
# 停止 apache 服务
systemctl stop httpd.service
-

RPM默认安装路径:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
路径说明
/etc一些设置文件放置的目录如 /etc/crontab
/usr/bin一些可执行文件
/usr/lib一些程序使用的动态函数库
/usr/share/doc一些基本的软件使用手册与帮助文档
/usr/share/man一些 man page 文件
-

Nginx

-

官方下载地址,选择需要的版本下载安装包(最新安装版本 1.14.0)

-
-

官方提供了.zip,.gz两种格式安装包

-
-

安装

-
    -
  • -

    方式一

    -
    # 1. 下载安装文件
    wget http://nginx.org/download/nginx-1.14.0.tar.gz
    # 2. 解压安装文件(解压到指定目录,常存放 /usr/local/ 路径)
    tar -zxvf nginx-1.14.0.tar.gz -C /usr/local/
    # 3. 编译安装依赖库
    cd /usr/local/nginx/
    ./configure
    -
  • -
  • -

    方式二

    -
    # 默认安装路径 /etc/nginx/
    yum install nginx
    -
  • -
-

常用命令

-
    -
  • 加压文件安装常用命令
    # 停止 ngix
    /usr/local/nginx/sbin/nginx -s quit
    # 重新载入 nginx(当配置信息发生修改时)
    /usr/local/nginx/sbin/nginx -s reload
    # 查看版本
    /usr/local/nginx/sbin/nginx -v
    # 查看 nginx 的配置文件的目录
    /usr/local/nginx/sbin/nginx -t
    # 查看帮助信息
    /usr/local/nginx/sbin/nginx -h
    -
  • -
  • yum安装常用命令
    # 启动
    systemctl start nginx
    # 停止
    systemctl stop nginx
    # 重启
    systemctl restart nginx
    # 查看运行状态
    systemctl status nginx
    # 开机启动
    systemctl enable nginx
    -
  • -
-

Node

-

官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.gz,.7z,zip等多种格式安装包

-
-

安装

-
# 1. 下载安装文件
wget https://nodejs.org/download/chakracore-release/v8.6.0/node-v8.6.0-linux-x64.tar.gz
# 2. 解压安装文件(解压到当前目录)
tar -zxf node-v8.6.0-linux-x64.tar.gz
# 3. 建立软链接,实现全局访问
ln -s /root/node-v8.6.0-linux-x64/bin/node /usr/local/bin/node
ln -s /root/node-v8.6.0-linux-x64/bin/npm /usr/local/bin/npm
-

Redis

-

官方下载地址,选择需要的版本下载安装包

-
-

官方提供了.gz格式安装包

-
-

安装

-
# 1. 下载安装文件
wget wget http://download.redis.io/releases/redis-4.0.10.tar.gz
# 2. 解压安装文件(解压到当前目录)
tar xzf redis-4.0.10.tar.gz
# 3. 编译安装
cd redis-4.0.10
# 编译
make
# 4. 启动服务
src/redis-server
-

配置

-
# 修改 redis.conf 文件中 daemonize 属性 为 yes
vim /you_install_path/redis.conf
-
-

其他配置根据自身需要调整修改

-
-

其他命令

-
    -
  1. 关闭服务
    redis-cli -h 127.0.0.1 -p 6379 shutdown
    -
  2. -
  3. 非安全模式启动
    # 后台以非安全模式启动
    nohup /usr/local/bin/redis-server --protected-mode no &
    -
  4. -
-

常用命令

-

文件查找

-

find

-

find 命令是根据文件的属性进行查找,如文件名,文件大小,所有者,所属组,是否为空,访问时间,修改时间等。

-
    -
  • 基本格式:
    -find path expression
  • -
  • 示例: -
      -
    • 在根目录下查找文件 httpd.conf,表示在整个硬盘查找
      -find / -name httpd.conf
    • -
    • 表示当前目录下查找文件名开头是字符串 srm 的文件
      -find . -name ‘srm*’
    • -
    • 查找在系统中最后 10 分钟访问的文件(access time)
      -find / -amin -10
    • -
    • 查找在系统中属于 fred 这个用户的文件
      -find / -user fred
    • -
    • 查找出小于 1000KB 的文件
      -find / -size -1000k
    • -
    -
  • -
-

grep

-

grep 是根据文件的内容进行查找,会对文件的每一行按照给定的模式(patter)进行匹配查找。

-
    -
  • 基本格式:
    -find expression
  • -
  • 主要参数:
    --c:只输出匹配行的计数。
    --i:不区分大小写
    --h:查询多文件时不显示文件名。
    --l:查询多文件时只输出包含匹配字符的文件名。
    --n:显示匹配行及行号。
    --s:不显示不存在或无匹配文本的错误信息。
    --v:显示不包含匹配文本的所有行。
  • -
  • 示例: -
      -
    • 显示所有包含每行字符串至少有 5 个连续小写字符的字符串的行
      -grep ‘[a-z]{5}’ aa
    • -
    • 显示所有以 d 开头的文件中包含 test 的行
      -grep ‘test’ d*
    • -
    -
  • -
-

进程相关

-
    -
  • -

    查看指定服务进程

    -
    # 查看 httpd 服务进程
    ps -ef | grep httpd
    # UID PID PPID C STIME TTY TIME CMD
    # root 7192 7103 0 19:59 pts/3 00:00:00 grep --color=auto httpd
    -
      -
    • UID:用户 ID
    • -
    • PID:进程 ID
    • -
    • PPID:父进程 ID
    • -
    • C:CPU 用于计算执行优先级的因子。数值越大,表明进程是 CPU 密集型运算,执行优先级会降低;数值越小,表明进程是 I/O 密集型运算,执行优先级会提高
    • -
    • STIME:进程启动的时间
    • -
    • TTY:完整的终端名称
    • -
    • TIME:CPU 时间
    • -
    • CMD:完整的启动进程所用的命令和参数
    • -
    -
    -
  • -
  • -

    杀死指定进程

    -
    kill -9 pid(逐个都删除)
    -
  • -
  • -

    查看指定端口

    -
    # 检测 6379 端口是否在监听  
    netstat -lntp | grep 6379
    -
  • -
-

文件复制

-

语法

-
scp(选项)(参数)
-

选项

-
-1:使用 ssh 协议版本 1;
-2:使用 ssh 协议版本 2;
-4:使用 ipv4;
-6:使用 ipv6;
-B:以批处理模式运行;
-C:使用压缩;
-F:指定ssh配置文件;
-i:identity_file 从指定文件中读取传输时使用的密钥文件(例如亚马逊云 pem),此参数直接传递给ssh;
-l:指定宽带限制;
-o:指定使用的 ssh 选项;
-P:指定远程主机的端口号;
-p:保留文件的最后修改时间,最后访问时间和权限模式;
-q:不显示复制进度;
-r:以递归方式复制。
-

参数

-
    -
  • 源文件:指定要复制的源文件
  • -
  • 目标文件:目标文件。格式为 user@host:filename(文件名为目标文件的名称)
  • -
-

示例

-
    -
  • -

    上传本地文件到远程机器指定目录

    -
    scp /opt/soft/nginx-0.5.38.tar.gz root@10.10.10.10:/opt/soft/scptest
    # 指定端口 2222
    scp -rp -P 2222 /opt/soft/nginx-0.5.38.tar.gz root@10.10.10.10:/opt/soft/scptest
    -
  • -
  • -

    上传本地目录到远程机器指定目录

    -
    scp -r /opt/soft/mongodb root@10.10.10.10:/opt/soft/scptest
    -
  • -
  • -

    从远程机器复制文件到本地

    -
    scp root@10.10.10.10:/opt/soft/nginx-0.5.38.tar.gz /opt/soft/
    -
  • -
  • -

    从远程机器复制文件(含目录)到本地

    -
    scp -r root@10.10.10.10:/opt/soft/mongodb /opt/soft/
    -
  • -
-

文件删除

-

语法

-
rm [选项] 文件或目录
-

选项

-
-f:强行删除,忽略不存在的文件,不提示确认。(f 为 force 的意思)
-i:进行交互式删除,即删除时会提示确认。(i 为 interactive 的意思)
-r:将参数中列出的全部目录和子目录进行递归删除。(r 为 recursive 的意思)
-v:详细显示删除操作进行的步骤。(v 为 verbose 的意思)
-

示例

-
    -
  • -

    删除一个文件

    -
    rm file
    -
  • -
  • -

    删除一个目录

    -
    rm file/
  • -
-]]>
- - Linux - - - Build - -
- - Linux 之 MySQL - /2018/07/23/linux-mysql/ - 之前粗略的接触了Linux的基础使用和安装,这次准备在自购的服务器上跑些应用,纯属娱乐,废话不说,上来就先仍数据库。
-数据库常用的Oracle,MySQL,SQL Server,MongoDB等,排名不分先后,自己平时接触最多的也就是MySQLMongoDB,好MySQL先来一份。

-

介绍

-

MySQL是一个开源数据库管理系统,通常作为流行的LEMP(Linux,Nginx,MySQL / MariaDB,PHP / Python / Perl)堆栈的一部分安装。它使用关系数据库和SQL(结构化查询语言)来管理其数据。

-

CentOS 7更喜欢MariaDB,它是由原始MySQL开发人员管理的MySQL分支,旨在替代MySQL。如果你在CentOS 7上运行 yum install mysql,那么安装的是MariaDB,而不是MySQL

- -

对于 MySQL 也是有好几个类别

-
    -
  1. MySQL Community Server:社区版本,开源免费,但不提供官方技术支持
  2. -
  3. MySQL Enterprise Edition:企业版本,需付费,可以试用 30 天
  4. -
  5. MySQL Cluster 集群版:开源免费。可将几个 MySQL Server 封装成一个 Server
  6. -
  7. MySQL Cluster CGE:高级集群版,需付费
  8. -
  9. MySQL Workbench:一款专为 MySQL 设计的 ER/数据库建模工具,分为两个版本 -
      -
    • MySQL Workbench OSS:社区版
    • -
    • MySQL WorkbenchSE:商用版
    • -
    -
  10. -
-

先检查服务器是否已经安装了mariadb

-
# 检查是否安装了 mariadb 客户端
rpm -qa | grep mariadb
# 检查是否安装了 mariadb 服务端
rpm -qa | grep mariadb-server
-

执行结果如下图,如果没有任何提示,则表示没有安装
-

-

执行检查命令,发现有 mariadb 服务,则需要先卸载 mariadb 相关服务

-
# mariadb 相关卸载
rpm -qa |grep mariadb |xargs yum remove -y
-

-

清单

-
    -
  • OS: CentOS 7
  • -
  • DataBase:MySQL 8.0.11
  • -
-
-

uname -a查看你 Linux 系统的信息,按照系统版本选择对应的应用

-
-
[dc2-user@10-255-0-191 ~]$ uname -a
Linux 10-255-0-191 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
# 这里,el7.x86_64 分别表示,el7:CentOS 7,x86_64:64 位系统
-

安装

-
-

官方下载 MySQL Community Server 地址:https://dev.mysql.com/downloads/mysql/
-清华镜像 MySQL 地址:https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/

-
-

-

在线安装(推荐方式)

-

适用于当前安装服务器可以正常互联网访问,如上图,选择方式进入MySQL Yum Repository,选择对应系统的版本,比如 mysql80-community-release-el7-3.noarch.rpm

-

-

根据自己设备网络情况选择使用的下载地址

-
    -
  • 官方地址:https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm
  • -
  • 清华镜像:https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql-8.0-community-el7-x86_64/mysql80-community-release-el7-3.noarch.rpm
  • -
-
# 1. 获取官方yum源安装包 mysql80-community-release-el7-3.noarch.rpm 是根据官网提供的版本信息
wget https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm
# 2. 安装rpm包
rpm -ivh mysql80-community-release-el7-3.noarch.rpm
# 3. 安装mysql-server
yum install -y mysql-server
# 如果你需要设置不区分大小写,必须在第一次启动 mysqld 服务前设置,lower_case_table_names=1
# vim /etc/my.cnf
# 4. 启动mysqld服务
systemctl start mysqld
# 5. 查看是否成功启动
ps aux|grep mysqld
# 6. 设置mysqld服务开机自启动
systemctl enable mysqld
-
-

设置不区分大小写参数,文件地址 /etc/my.cnf

-
-

离线安装

-

适用于当前安装服务器无法互联网访问,如安装截图所示,选择 MySQL Community Server 类别,然后选择对应系统及版本进行下载,总共需要如下所示相关的应用安装包

-

按照此顺序进行安装

-
rpm -ivh mysql-community-common-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-plugins-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-compat-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-embedded-compat-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-devel-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-8.0.22-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-8.0.22-1.el7.x86_64.rpm
-

安装 mysql-community-devel-8.0.22-1.el7.x86_64.rpm 时出错,需要先执行 yum install openssl-devel 命令进行安装 openssl-devel,如下图
-

-

安装完上面的包,进行启动 MySQL 服务,并进行相应的配置

-
# 如果你需要设置不区分大小写,必须在第一次启动 mysqld 服务前设置,lower_case_table_names=1
# vim /etc/my.cnf
# 启动mysqld服务
systemctl start mysqld
# 查看是否成功启动
ps aux|grep mysqld
# 设置mysqld服务开机自启动
systemctl enable mysqld
-

整个安装设置过程如下图
-

-

配置

-

由于MySQL从5.7开始不允许在首次安装后,使用空密码进行登录,系统会随机生成一个密码以供管理员首次登录使用,这个密码记录在/var/log/mysqld.log文件中

-
# 1. 查看系统提供密码
cat /var/log/mysqld.log|grep 'A temporary password'
# 2. 使用获取到的密码登录MySQL
mysql -u root -p
# 3. 切换数据库
use mysql;
# 4. 修改root密码 your_password 替换成你自己的密码就可以了,这个密码是强密码,要求密码包含大小写字母、数字及标点符号,长度大于6
alter user 'root'@'localhost' identified by 'your_password';
# 5. 刷新修改
flush privileges;
-

默认信息

-

配置信息地址

-

默认安装配置信息:/etc/my.cnf

-

MySQL 安装路径

-
# 查看 MySQL 安装的位置
which mysqld
-

其他配置信息

-

-

连接

-

自己平时习惯使用 Navicat 进行数据库操作,因此这里进行配置链接已在云端刚刚安装的MySQL服务
-linux-mysql

-

ERROR 2003

-

通过本地的工具无法连接到服务器上的 MySQL 服务,错误提示:ERROR 2003 (HY000): Can't connect to MySQL server on '116.85.58.6' (60)

-

进行原因排查,分别通过 pingtelnet 命令来检查网络连接情况,发现 IP 是通的,端口不同,那么去看服务器是不是开启了防火墙,如果开启了防火墙,需要将 MySQL 服务的端口排除

-

对于开启了防火墙的,可以将防火墙关闭,或者设置端口白名单

-
-
# 查看防火墙状态,结果显示为running或not running
## 如果显示,-bash: firewall-cmd: command not found,则表示当前服务器没有安装防火墙
firewall-cmd --state
# 关闭防火墙firewall
systemctl stop firewalld.service
systemctl disable firewalld.service
# 关闭防火墙firewall后开启
systemctl start firewalld.service
# 开启端口
## zone -- 作用域
## add-port=80/tcp -- 添加端口,格式为:端口/通讯协议
## permanent -- 永久生效,没有此参数重启后失效
firewall-cmd --zone=public --add-port=3306/tcp --permanent
## 开启3306端口后,workbench或naivcat 就能连接到MySQL数据库了
-

-

SSH 连接 MySQL 所在的服务器,查看防火墙情况,但发现压根没有防火墙,那么此时,要去看看你的云服务器的安全组设置规则,将 3306 暴露出来,查看云服务器安全组设置
-

-

在已有安全组中添加安全规则或者新增安全组在新的安全组中添加安全规则
-

-

ERROR 1130

-

按照上图图的配置信息链接MySQL,发现错误提示:ERROR 1130: Host 'xxx.xxx.xxx.xxx' is not allowed to connect to this MySQL server

-

原因

-

不允许从远程登陆MySQL服务,只能在localhost

-

解决方法

-
# 切换数据库
use mysql;
# 修改user 指定的host 为 %
update user set host = '%' where user = 'root';
# 查看 user 表中的信息
select host, user from user;
# 成功修改s
+-----------+------------------+
| host | user |
+-----------+------------------+
| % | root |
| localhost | mysql.infoschema |
| localhost | mysql.session |
| localhost | mysql.sys |
+-----------+------------------+
4 rows in set (0.00 sec)
-

ERROR 2059

-

继续重试链接,错误提示:ERROR 2059: Authentication plugin 'caching_sha2_password' cannot be loaded:The specified module could not be found.

-

原因

-

MySQL 8不支持动态修改密码验证方式

-

解决方法

-
方法一
-

修改plugin默认的 caching_sha2_passwordmysql_native_password

-
# 停止mysql
systemctl stop mysqld.service
# my.cnf文件中默认有下面的语句,删除前面的#号即可,没有的话就把它添加到my.cnf中 ,默认路径`/etc/my.cnf`
default-authentication-plugin=mysql_native_password
# 切换数据库
use mysql
# 给指定用户设置密码,这里`%`是因为之前已经将远程没有特殊指定,用%代替了localhost
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'your_password';
-
方法二
-

更新你的数据库连接工具,Navicat version12+,Workbench version8+

-

BDB1507

-

BDB1507 Thread died in Berkeley DB library,由于其他操作原因造成数据库被破坏问题,可以通过如下命令进行数据库修复

-
cd /var/lib/rpm/
for i in `ls | grep 'db.'`;do mv $i $i.bak;done
rpm --rebuilddb
yum clean all
-

-

卸载

-

应用卸载

-
# 快速删除
yum remove mysql mysql-server mysql-libs mysql-server
# 查找残留文件(例如:mysql-community-server-8.0.22-1.el7.x86_64)
rpm -qa | grep -i mysql
# 将查询出来的应用删除
yum remove mysql-community-server-8.0.22-1.el7.x86_64
# 查找残余目录
## /etc/selinux/targeted/active/modules/100/mysql
## /etc/selinux/targeted/tmp/modules/100/mysql
## /var/lib/mysql
## /var/lib/mysql/mysql
## /usr/share/mysql
find / -name mysql
# 删除残余目录
rm -rf /etc/selinux/targeted/active/modules/100/mysql
rm -rf /etc/selinux/targeted/tmp/modules/100/mysql
rm -rf /var/lib/mysql
rm -rf /var/lib/mysql/mysql
rm -rf /usr/share/mysql
# 再次检查残余目录,确保没有残余
find / -name mysql
-

开机自启(可选)

-

当你安装时设置了开机启动,当不需要再开机启动时,那么再卸载完数据库后,记得在开启启动的列表中删除数据库

-
chkconfig --list | grep -i mysql
chkconfig --del mysqld
-

附录

-
    -
  • How To Install MySQL on CentOS 7
  • -
  • ERROR 1130
  • -
  • ERROR 2059
  • -
-]]>
- - Linux - - - MySQL - -
- - Lombok - /2019/08/21/lombok/ - 在实际开发过程中,不管是服务端(Java),还是客户端(Android)都需要创建对应的实例bean对象,用来实例化对象,在对需要实例化的对象中,通常需要写 setget 方法,字段少的时候,还能忍受,尤其当客户端字段多的时候,而且字段类型或者字段名称来回改动时,稍不注意,就很大机率修改不全面,就会造成一些隐藏bug,有没有不需要手动取写(快捷键生成)这些方法,当然有,本片文章,我们就来学习 Lombok

-

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
-Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more(Project Lombok是一个java库,可以自动插入编辑器并构建工具,为您的java增添色彩。 永远不要再写另一个getter或equals方法,使用一个注释,你的类有一个功能齐全的构建器,自动化你的日志记录变量等等),这是官方对Lombok的介绍,简单来讲就是通过注解的方式,代替一些重复性的代码,让代码更加简洁

- -

安装集成

-
    -
  • 编辑器(IDEA or Android Studio)安装 Lombok 插件,File -> Settings... -> Plugins -> 搜索 Lombok 并安装
  • -
  • 开启编辑器 Annotation Processors
    -
  • -
  • 项目集成Lombok依赖,项目按照不同的包管理, 按照对应方式添加包依赖
    <!-- gradle 集成 -->
    <!-- https://projectlombok.org/setup/gradle -->
    repositories {
    mavenCentral()
    }

    dependencies {
    implementationOnly 'org.projectlombok:lombok:1.18.8'
    annotationProcessor 'org.projectlombok:lombok:1.18.8'
    }

    <!-- maven集成 -->
    <!-- https://projectlombok.org/setup/maven -->
    <dependencies>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
    <scope>provided</scope>
    </dependency>
    </dependencies>
    -
  • -
-

稳定注解

-

Lombok提供稳定的注解,可以直接在生成环境使用,org.projectlombok.lomboklombok路径下

-

val

-

val 作为任何局部变量声明类型(包含 for语句声明),并且该类型会推断初始化表达式的类型,同时也是final的。

-
    -
  • 0.10版本加入该功能
  • -
  • 使用场景:局部变量声明
  • -
-
// x 将被推断为 double 类型,并且是final
val x = 10.0;
// y 将被推断为 ArrayList<String> 类型,并且是final
val y = new ArrayList<String>();
// z 将被转换为 final int z = 10;
val z = 10;
-
-

官方示例val

-
-

var

-

var 作为任何局部变量声明类型(包含 for语句声明),并且该类型会推断初始化表达式的类型。

-
    -
  • 1.16.20版本加入该功能
  • -
  • 使用场景:局部变量声明
  • -
-
// x 将被推断为double,转换为 double x = 10.0d;
var x = 10.0;
-
-

官方示例var

-
-

@NonNull

-

注解在 属性 上,会自动产生一个关于此参数的非空检查,如果参数为空,则抛出一个空指针异常,也会有一个默认的无参构造方法

-
    -
  • 0.11.10版本加入该功能
  • -
  • 支持字段,方法,参数,局部变量,枚举
  • -
-
-

官方示例@NonNull

-
-

@Cleanup

-

自动资源管理,安全地调用close()方法,确保通过调用 close() 方法清除你注释的变量声明,无论发生声明情况

-
    -
  • 支持局部变量
  • -
-
public void copyFile(String in, String out) throws IOException {
@Cleanup FileInputStream inStream = new FileInputStream(in);
@Cleanup FileOutputStream outStream = new FileOutputStream(out);
byte[] b = new byte[65536];
while (true) {
int r = inStream.read(b);
if (r == -1) break;
outStream.write(b, 0, r);
}
}
-
-

官方示例@Cleanup

-
-

@Getter/@Setter

-
    -
  • 注解在 属性 上;为单个属性提供 set/get 方法;
  • -
  • 注解在 上,为该类所有的属性提供 set/get 方法, 都提供默认构造方法
  • -
  • 等级可自己更改 PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE,默认Public
  • -
-
// 可手动设置字段的set方法为你需要的修饰类型
@Setter(AccessLevel.PROTECTED)
private String name;
-
-

官方示例@Getter/@Setter

-
-

@ToString

-

任何 定义都可以用 @ToString 注释,让lombok生成 toString() 方法的实现。默认情况下,它会按顺序打印您的类名以及每个字段,并以逗号分隔

-
    -
  • includeFieldNames,默认true:打印时包括每个字段的名称
  • -
  • callSuper,默认false:在输出中包含超类的实现的结果
  • -
  • doNotUseGetters,默认false:通常情况下,如果有可用的getter,那么就会调用它们。要禁止此操作并让生成的代码直接使用这些字段,请将其设置为true
  • -
  • onlyExplicitlyIncluded,默认false:仅包含用明确标记的字段和方法。通常,默认情况下包含所有(非静态)字段
  • -
-
// 排除该字段一同生成在 toString()方法中
@ToString.Exclude
private int id;
// 配置在toString中呈现此成员的行为;如果在方法上,请在输出中包含方法的返回值
// rank(默认为0):首先打印更高的等级。相同级别的成员按照它们在源文件中出现的顺序打印
// name(默认为""):默认为带注释的成员的 字段/方法 名称。如果名称等于默认包含字段的名称,则此成员将取代它
@ToString.Include(rank = 0, name = "")
private int id;
-
-

官方示例@ToString

-
-

@EqualsAndHashCode

-

注解在 上, 可以生成 equals、canEqual、hashCode 方法,与 @Data 相比,少了 toString() 方法,部分属性和 @ToString 注解相同

-
-

官方示例@EqualsAndHashCode

-
-

Constructor

-

按照定制生成构造函数

-
    -
  • @NoArgsConstructor:生成不带参数的构造函数
  • -
  • @RequiredArgsConstructor:生成带有必需参数的构造函数。参数是final字段和字段是具有约束的,例如@NonNull
  • -
  • @AllArgsConstructor:生成一个全参构造函数
  • -
-
-

官方示例Constructor

-
-

@Data

-

生成所有字段的 getter,一个有用的 toString 方法,以及检查所有 non-transient 字段的 hashCode 和 equals 实现。还将为所有 non-final 字段以及构造函数生成setter

-
-

官方示例@Data

-
-

@Value

-

@Value 是 @Data 的变体版,默认情况下,所有字段都是 privatefinal 的,并且不会生成setter,默认情况下,class 本身也是 final 的,因为不可变性不是可以强制进入子类的东西

-
    -
  • 0.12.0版本加入稳定的该功能
  • -
-
-

官方示例@Value

-
-

@Builder

-
    -
  • 对成员注解,则它必须是构造函数或方法
  • -
  • 对类(class)注解,那么将生成一个私有构造函数,所有字段都作为参数(就好像类上有@AllArgsConstructor(access = AccessLevel.PRIVATE)),并且这个构造函数已经被@Builder注释了
  • -
-
-

注意,只有当您没有编写任何构造函数,也没有添加任何显式的@XArgsConstructor注释时,才会生成这个构造函数。在这些情况下,lombok将假定存在一个all-args构造函数,并生成使用它的代码;这意味着如果没有这个构造函数,就会出现编译器错误。

-
-
-

官方示例@Builder

-
-

@SneakyThrows

-
    -
  • 捕获或抛出方法主体中语句声明它们生成的任何已检查异常
  • -
  • SneakyThrows不会下沉,封装到RuntimeException中,或者以其他方式修改列出的已检查异常类型的任何异常
  • -
-
// lombok 写法
@SneakyThrows(UnsupportedEncodingException.class)
public void utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}

// 等价于下面写法
public void utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException $uniqueName) {
throw useMagicTrickeryToHideThisFromTheCompiler($uniqueName);
// This trickery involves a bytecode transformer run automatically during the final stages of compilation;
// there is no runtime dependency on lombok.
}
}
-
-

官方示例@SneakyThrows

-
-

@Synchronized

-

@Synchronized几乎与将“synchronized”关键字放在方法上完全一样,只不过它将同步到一个私有内部对象上,这样其他不在您控制之下的代码就不会通过锁定自己的实例来干扰线程管理

-
    -
  • 对于 非静态 方法,使用一个名为 $lock 的字段
  • -
  • 对于 静态 方法,则注释会锁定名为 $LOCK 的静态字段
  • -
-
-

官方示例@Synchronized

-
-

@Getter(lazy=true)

-

适用于那些计算占用大量 CPU,或者占用较大内存时,该注解很有用

-
-

官方示例@GetterLazy

-
-

@Log

-

注解路径lombok.extern.java路径下,相关的 Log 有已下几种

-
    -
  • @CommonsLog
    private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
    -
  • -
  • @Flogger
    private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass();
    -
  • -
  • @JBossLog
    private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
    -
  • -
  • @Log
    private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
    -
  • -
  • @Log4j
    private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
    -
  • -
  • @Log4j2
    private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
    -
  • -
  • @Slf4j
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
    -
  • -
  • @XSlf4j
    private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
    -
  • -
-
-

官方示例@log

-
-

实验注解

-

Lombok提供实验性注解,请根据实际情况取舍,org.projectlombok.lomboklombok.experimental路径下

-

@Accessors

-

@ExtensionMethod

-

@FieldDefaults

-

@Delegate

-

@Wither

-

onX

-
    -
  • onMethod
  • -
  • onConstructor
  • -
  • onParam
  • -
-

UtilityClass

-

Helper

-

FieldNameConstants

-

SuperBuilder

-

进阶配置

-

Configuration system

-

其他

-

如果在对Android项目进行升级使用 Lombok 代替原来的写法,在很大程度上还是会遇到如下截图问题
-lombok-error
-根据提示项目已经开启了 Annotation Processors,但是在每次打开项目都会提示错误信息

-

解决方法

-

Setting for all projects

-
    -
  1. File -> Other Settings -> Settings for new projects -> Build, Execution, Deployment -> Compiler ->Annotation Processors
  2. -
  3. Enable Annotation Processing
  4. -
  5. Click Apply
  6. -
  7. Restart Your Android studio
  8. -
-

一些旧项目还需要额外的一些操作

-
    -
  1. 删除项目根路径下的 yourProject.iml 文件以及 .idea 目录 或者你可以 File -> Invalidate Caches / Restart… 操作
  2. -
  3. 重新打开项目
  4. -
-
-

参考issues264

-
-]]>
- - Util - - - Util - -
- - 应该知道的系统环境配置文件 - /2018/11/24/mac-bash/ - 在计算机操作系统中Shell是用户与操作系统交互的媒介,而bash作为目前Linux\macOS系统中最常用的Shell,它支持的startup文件也并不单一,甚至让人感到费解,以下就是对Shell的学习

-

Shell:在计算机中,值“为用户提供用户界面”的软件,通常指的是 命令行界面 的解析器。一般来说,Shell指操作系统中提供访问内核所提供的服务程序。

- -

通常将Shell分为两类

-
    -
  • 命令行:提供一个命令行界面(CLI)
  • -
  • 图形界面:提供一个图形用户界面(GUI)
  • -
-

linux_system

-

在PC桌面领域,不同的操作系统都有自己的Shell,截止2018.10主流的操作系统市场占有率,Windows(78.04%),OS X(13.73%),Unknown(5.44%),Linux(1.64%),Chrome(1.15%),数据来源于statcounter

-

这些操作系统中都有自己独特的Shell命令,在不同的系统版本中,命令工具也是不完全相同,例如:

-
    -
  • Windows:Windows CE、Windows NT常用cmd.exe;Windows 10中常用PowerShell
  • -
  • OS X:默认bash,除此之外还提供了tcshzshksh
  • -
  • Linux:/etc/shells路径下,/bin/sh/bin/bash/bin/csh等应用
  • -
-
-

更详细的请查阅维基百科

-
-

Configuration Files

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
文件shkshcshtcshbashzsh
/etc/.loginloginlogin
/etc/csh.cshrcyesyes
/etc/csh.loginloginlogin
~/.tcshrcyes
~/.cshrcyesyes
~/etc/ksh.kshrcint.
/etc/sh.shrcint.
$ENV (typically ~/.kshrc)int.int.int.
~/.loginloginlogin
~/.logoutloginlogin
/etc/profileloginloginloginlogin
~/.profileloginloginloginlogin
~/.bash_profilelogin
~/.bash_loginlogin
~/.bash_logoutlogin
~/.bashrcint.+n/login
/etc/zshenvyes
/etc/zprofilelogin
/etc/zshrcint.
/etc/zloginlogin
/etc/zlogoutlogin
~/.zshenvyes
~/.zprofilelogin
~/.zshrcint.
~/.zloginlogin
-
    -
  • yes:表示shell在启动时始终读取文件
  • -
  • login:表示如果shell是登录shell,则读取文件
  • -
  • n/login:表示如果shell不是登录shell,则读取文件
  • -
  • int.:表示如果shell是交互式的,则读取文件
  • -
-
-

更详细的介绍请查阅维基百科

-
-

关于常用Shell,执行流程如下图:
-flow

-

startup文件

-

bash作为目前LinuxmacOS(默认bash命令)系统中最常用的shell,通过上面的表格,我们可以知道macOS系统中,bash主要由以下文件

-
    -
  • /etc/profile:The systemwide initialization file, executed for login shells
  • -
  • ~/.profile:
  • -
  • ~/.bash_profile:The personal initialization file, executed for login shells
  • -
  • ~/.bash_login:
  • -
  • ~/.bash_logout:The individual login shell cleanup file, executed when a login shell exits
  • -
  • ~/.bashrc:The individual per-interactive-shell startup file
  • -
-

我们看看在macOS系统中,bash的startup文件是如何进行加载

-

注意:

-
    -
  • /etc/profile/etc/paths是系统级别,系统启动后就会加载,后面的配置文件是当前用户级的环境变量
  • -
  • 如果~/.bash_profile存在,后面几个文件就会忽略不读,不在时,才会以此类推读取后面的文件
  • -
  • ~/.bashrc没有上述规则,他始终加载,它是在bash shell打开的时候载入的
  • -
-
-

特点

-

bash的两种属性,即 “交互”“登录”,按照bash是否与用户进行交互,可将其分为 “交互式”“非交互式”;按照bash是否被用户登录,又可将其分为 “登录shell”“非登录shell”

-

交互式与非交互式

-
    -
  • 交互式:shell的一种运行模式,交互式shell等待用户输入命令,并且立即执行,然后将结果反馈给用户。整个流程:登录——>执行命令——>退出。当你退出后,这个shell就终止
  • -
  • 非交互式:shell的另一种运行模式,它专门用来执行预先设定的命令。这种模式下,shell不予用户进行交互,而是读取存储在脚本文件中的命令并执行它们。当它读取到文件结尾,这个shell就终止
  • -
-

登录shell与非登录shell

-
    -
  • 登录shell: -
      -
    • 用户通过输入用户名/密码(或者证书认证)后启动的shell;
    • -
    • 通过带有-l|--login参数的bash命令启动的shell
      -例如:系统启动,远程启动,使用su -切换用户,通过bash --login命令启动的bash等
    • -
    -
  • -
  • 非登录shell:以上情况除外基本就是 “非登录shell”
    -例如:从图形化界面启动终端,使用su -切换用户,通过bash命令启动bash等
  • -
-

主要区别

-
    -
  • 使用logout退出登录shell,使用exit退出非登录shell
  • -
  • 其实exit命令会判断当前shell的登录属性,并且分别调用logoutexit指令
  • -
  • 登录shell非登录shell的主要区别在于启动shell时所执行的startup文件不同;登录shell执行的startup文件为~/.bash_profile,而非登录shell执行的startup文件为~/.bashrc
  • -
-

总结

-

Path语法

-
# 中间使用冒号分隔
export PATH=$PATH:<PATH 1>:<PATH 2>:<PATH 3>:------:<PATH N>
-

环境变量设置

-

全局设置

-
    -
  • /etc/paths:全局环境变量设置,建议修改此文件
  • -
  • /etc/profile:不建议修改此文件,全局配置,不管是哪个用户,登录时都会读取此文件
  • -
  • /etc/bashrc:一般在这个文件中添加系统级别环境变量,全局配置,bash shell执行时,不管是何种方式,都会读取此文件
  • -
-

单用户设置

-
    -
  • ~/.bash_profile:添加用户级环境变量
    -例如:设置ANDROID_HOME到PATH
    export ANDROID_HOME=/Users/shaoc/Library/Android/sdk
    export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH
    -
  • -
  • ~/.bashrc 同上
    -一般重启shell设置就会生效,如果想立刻生效,则可执行下面的语句:
    source 相应的文件
    -
  • -
-

zsh中配置环境变量

-

在安装 oh my zsh后,.bash_profile文件中的环境变量就无法起到作用,因为终端默认启动的是zsh,而不是shell,所以无法加载

-
    -
  • -

    解决方法
    -在~/.zshrc配置文件中,增加对.bash_profile的引用:

    -
    source ~/.bash_profile
    -

    .bash_profile文件示例:

    -
    export ANDROID_HOME=/Users/blade/Library/Android/sdk
    export GRADLE_HOME=/Users/blade/Documents/DevTools/Gradle/gradle-4.6
    export FLUTTER_HOME=/Users/blade/Documents/DevTools/flutter
    export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$GRADLE_HOME/bin:$FLUTTER_HOME/bin:$PATH
    -
  • -
-

附录

-
    -
  • 原关于“.bash_profile”和“.bashrc”区别的总结
  • -
  • Mac环境变量配置
  • -
-]]>
- - macOS - - - Shell - -
- - Aria2 之 macOS - /2018/12/12/mac-download/ - Aria2 是什么 -

Aria2 是一款支持多种协议的 轻量级命令行 下载工具。有以下特性:

-
    -
  • 多线程连线:Aria2 会自动从多个线程下载文件,并充分利用你的带宽;
  • -
  • 轻量:运行时不会占用过多资源,根据官方介绍,内存占用通常在 4MB~9MB,使用 BitTorrent 协议,下行速度 2.8MB/s 时 CPU 占用率约 6%;
  • -
  • 全功能 BitTorrent 客户端;
  • -
  • 支持 RPC 界面远程控制
  • -
- -

Aria2 安装

-
brew install aria2
-
-

Homebrew是一款自由及开放源代码的软件包管理系统,用以简化Mac OS X系统上的软件安装过程,以Ruby语言写成,默认安装在/usr/local

-
-

Aria2 配置

-
# 进入~路径
cd ~
# 创建.aria2文件夹
mkdir .aria2
# 创建aria2.conf配置文件
touch aria2.conf
-

复制以下内容保存在aria2.conf文件中,修改 dir=/Users/blade/Downloads路径即可

-
#用户名
#rpc-user=user
#密码
#rpc-passwd=passwd
#上面的认证方式不建议使用,建议使用下面的token方式
#设置加密的密钥
#rpc-secret=token
#允许rpc
enable-rpc=true
#允许所有来源, web界面跨域权限需要
rpc-allow-origin-all=true
#允许外部访问,false的话只监听本地端口
rpc-listen-all=true
#RPC端口, 仅当默认端口被占用时修改
rpc-listen-port=6800
#最大同时下载数(任务数), 路由建议值: 3
max-concurrent-downloads=5
#断点续传
continue=true
#同服务器连接数
max-connection-per-server=5
#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要
min-split-size=10M
#单文件最大线程数, 路由建议值: 5
split=10
#下载速度限制
max-overall-download-limit=0
#单文件速度限制
max-download-limit=0
#上传速度限制
max-overall-upload-limit=0
#单文件速度限制
max-upload-limit=0
#断开速度过慢的连接
#lowest-speed-limit=0
#验证用,需要1.16.1之后的release版本
#referer=*
#文件保存路径, 默认为当前启动位置
dir=/Users/blade/Downloads
#文件缓存, 使用内置的文件缓存, 如果你不相信Linux内核文件缓存和磁盘内置缓存时使用, 需要1.16及以上版本
#disk-cache=0
#另一种Linux文件缓存方式, 使用前确保您使用的内核支持此选项, 需要1.15及以上版本(?)
#enable-mmap=true
#文件预分配, 能有效降低文件碎片, 提高磁盘性能. 缺点是预分配时间较长
#所需时间 none < falloc ? trunc << prealloc, falloc和trunc需要文件系统和内核支持
file-allocation=prealloc
-

开启 Aria2

-

终端中输入,其中xxx是你的电脑用户名

-
aria2c --conf-path="/Users/xxx/.aria2/aria2.conf" -D
-

Aria2 开机自启

-
    -
  1. 创建aria2.plist文件
    cd ~/Library/LaunchAgents
    touch aria2.plist
    -
  2. -
  3. 修改aria2.plist文件内容,其中<array></array>中的值改为自己电脑上 aria2c 命令的路径,可以在终端输入which aria2c查看,将WorkingDirectory后面的<string></string>中的值改为自己的下载路径
    <?xml version="1.0"encoding="utf-8"?>
    <!DOCTYPE plist PUBLIC"-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    <key>KeepAlive</key>
    <true />
    <key>RunAtLoad</key>
    <true />
    <key>Label</key>
    <string>aria2</string>
    <key>ProgramArguments</key>
    <array>
    <string>/usr/local/bin/aria2c</string>
    </array>
    <key>WorkingDirectory</key>
    <string>/Users/blade/Downloads</string>
    </dict>
    </plist>
    -
  4. -
-

启用Web

-

其实,如果你喜欢使用命令来操作,那么此步可跳过

-
# 获取项目代码
git clone https://github.com/ziahamza/webui-aria2
# 打开 index.html 文件
cd webui-aria2/docs
open index.html
-

其他

-

进行brew更新警告

-

警告内容:Unbrewed header files were found in /usr/local/include ...
-原因:系统中已存在下面列表中包含的包内容不是通过brew进行安装
-解决方法:删除那些文件就可以了

-
# 或者获取sudo权限删除
sudo rm -rf ‘/usr/local/bin/node’
# 重新安装node
brew install node
-

附录

-
    -
  • Mac安装使用aria2,AriaNg下载百度网盘资源
  • -
  • 如何配置 Aria2 来进行文件下载
  • -
  • Aria2 - 下载神器
  • -
  • Aria2 命令使用参考文档
  • -
-]]>
- - macOS - - - Download - -
- - MacBook Pro 初始化 - /2018/11/10/mac-init/ - 今天拿到了一辆跑车 MBP,虽然不是顶配,也能算上中等吧,废话不啰嗦,上来就是一顿操作猛如虎,最终效果就是唬

-

跑车的一些零配件来源地Awesome MacMacWK 一些破解软件集合地

-

软件的安装,这里不再赘述,这里主要对常用开发软件的配置进行记录

- -

JDK

-

作为Android开发者,JDK的安装那是少不了

-

下载

-

在Oracle 官网下载所需JDK 版本,这里举例:JDK1.8.0_191

-

安装

-

此处省略,简单的安装步骤

-

配置

-

以下命令相关操作,均在自带系统终端应用或者自己安装的其他终端命令工具

-
# 查看安装的Java版本
java -version
# 编辑profile文件
sudo vim /etc/profile
# 在打开的 profile 文件中,最下面加入以下文本,添加完成后,保存退出
JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/"

CLASS_PATH="$JAVA_HOME/lib"

PATH=".:$PATH:$JAVA_HOME/bin"

# 使配置生效
source /etc/profile
# 查看JAVA_HOME是否正确
echo $JAVA_HOME
-

jdk-command

-

jdk-config

-
-

注意:

-
    -
  1. JAVA_HOMEjdk1.8.0_191.jdk 是自己安装对应版本的文件夹,可以在Finder中,快捷键:Command + Shift + G,输入: /Library/Java/JavaVirtualMachines/ ,最终得到对应的文件夹名,如: jdk1.8.0_191.jdk
  2. -
  3. vim模式下,输入“i”:表示,插入,“esc”:表示退出编辑模式,“:wq!”:表示保存并退出
  4. -
-
-

MySQL

-

下载

-

官方下载地址:https://dev.mysql.com/downloads/

-

mac-mysql-downlaod

-

安装

-

mac-mysql-install

-

我这里选择自定义设置密码,请记住你设置的密码,最好是大小写+数字+字符的组合方式

-

登录

-
# 使用 root 账号登录 MySQL
mysql -u root -p
# 出现下图中的MySQL,表示成功连接 MySQL
-

mac-login-mysql

-

卸载

-

mac-mysql-uninstall

-

Git

-

直接在自带系统终端应用中,输入 git --version ,由于之前并没有安装,系统会提示,直接同意并安装即可

-

GitHub配置

-
# 查看本地是否生成过秘钥,如果该文件夹不存在,则表示未生成过秘钥
cd ~/.ssh
# 生成一个github的秘钥,这里github可以根据喜好自己命名(默认.ssh路径,不添加密码等操作,直接三次回车,即可生成秘钥)
ssh-keygen -t rsa -C "github"
# 查看公钥
cat ~/.ssh/id_rsa.pub
# 复制公钥添加到GitHub的SSH设置中,这里省略操作截图步骤
-

mac-github-config

-

Gradle配置

-
    -
  • -

    下载地址:官网,下载-all版本

    -
  • -
  • -

    设置GRADLE_HOME路径

    -
    # 打开.bash_profile文件
    open -e .bash_profile
    # Gradle_HOME环境设置,并保存
    GRADLE_HOME=/Users/blade/Documents/DevTools/Gradle/gradle-4.6
    export GRADLE_HOME
    export PATH=$PATH:$GRADLE_HOME/bin
    # 配置文件生效
    source ~/.bash_profile
    # 验证配置
    gradle -version
    -
    -

    如果提示The file /Users/blade/.bash_profile does not exist.则在根路径下创建 .bash_profile 文件
    -执行命令 touch .bash_profilesss

    -
    -

    gradle-config

    -
  • -
-

Keka

-

对于 macOS 上的文件解压缩工具,有The UnarchiverBetterZipKeka等,我这里使用 keka,毕竟开源:https://github.com/aonez/Keka,真香

-
# 安装
brew cask install keka
# 卸载
brew cask zap keka
-
-

Mac 压缩 / 解压缩工具解决方案

-
-

OpenInTerminal

-

OpenInTerminal 是在 Finder 上的一个扩展工具,能够快速在当前位置已命令行或者指定的编辑器打开,非常方便,对于 OpenInTerminal 和 OpenInTerminal-Lite,OpenInEditor-Lite 的区别

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FeaturesOpenInTerminalOpenInTerminal-Lite & OpenInEditor-Lite
Support Terminal, iTerm, Hyper, Alacritty and kitty.
Support TextEdit, Visual Studio Code, VSCode Insiders, Atom, Sublime Text, VSCodium, BBEdit, TextMate, CotEditor, MacVim and JetBrains(AppCode, CLion, GoLand, IntelliJ IDEA, PhpStorm, PyCharm, RubyMine, WebStorm).
Set to open a new tab or window.
Support English, Chinese, French, Russian, Italian and Spanish.
Copy path of the selected file or Finder window to Clipboard
GUI preferences
Support keyboard shortcuts.
Support Dark Mode.
-

安装

-
    -
  • openinterminal
    brew cask install openinterminal
    -
  • -
  • openinterminal-lite & openineditor-lite
    brew cask install openinterminal-lite
    brew cask install openineditor-lite
    -
  • -
-

配置

-

这里主要是对应用进行授权

-
    -
  • openinterminal
    -System Preferences(系统偏好设置) -> Extensions(扩展) -> Finder Extensions(访达扩展)
  • -
  • openinterminal-lite & openineditor-lite
    -拖拽 openinterminal-lite & openineditor-lite 到你的 Finder 的状态栏上
  • -
-

macOS 快捷键

-

顺带记录自己常用的快捷键吧🙃,其他用的时候在边用边查找吧

-
-

Mac 键盘快捷键

-
-]]>
- - macOS - - - Exp - Build - -
- - iTerm2 日常 - /2020/08/05/mac-item2/ -

-

iTerm2是一款优秀强大的第三方终端,相信用 Mac 的开发者,一定听过或者用过 iTerm2 这款终端应用,如果你还没使用过,没关系那么本篇文章就带你了解学习 iTerm2 中的一些常用操作,来提高你的工作效率

- -

安装配置

-

下载

-

iTerm2官网

-
-

注意:
-系统自带的终端默认使用bash;iTerm2默认使用zsh,因此两者切换如下命令

-
-
# 安装完iTerm 可使用如下命令来切换
chsh -s /bin/[zsh | bash]
-

配置

-

基础设置

-
    -
  1. -

    默认应用
    -MenuBar -> iTerm2 -> Make iTerm2 Default Term
    -iterm2-default

    -
  2. -
  3. -

    全局热键
    -MenuBar -> iTerm2 -> preference -> Keys -> Show/hide iTerm2 with a system-wide hotkey
    -输入设置的快捷键,这里使用⌘,
    -iterm2-hotkey

    -
  4. -
-

安装Oh my zsh

-
    -
  • 方式一:crul
    sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
    -
  • -
  • 方式二:wget
    sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
    -zsh-install
  • -
-

PowerLine

-
pip install powerline-status --user
-
-

如果提示:
-command not found: pip
-先执行,再执行上面的字体安装命令

-
-
sudo easy_install pip
-

安装PowerFonts

-

为避免后续的使用中,可能会遇到字符乱码的问题,因此安装字体

-

字体库需要首先将项目clone到本地,然后执行源码中的install.sh,根据自己的喜好存放在指定的位置

-
# 进入Documents目录
cd Documents
# 创建文件夹PowerFonts
mkdir PowerFonts
# 进入PowerFonts目录
cd PowerFonts
# clone源码
git clone https://github.com/powerline/fonts.git --depth=1
# 进入fonts目录
cd fonts
# 执行安装脚本
./install.sh
-

设置字体及背景

-
    -
  • -

    设置字体
    -MenuBar -> iTerm2 -> Preferences -> Profiles -> Text -> Change Font,选择Meslo LG字体,L,M,S风格,看个人喜好,这里选择Meslo LG S Powerline
    -iterm2-font

    -
  • -
  • -

    背景设置
    -iterm2-presets

    -
  • -
-

修改主题

-
# 打开.zshrc隐藏文件
vim ~/.zshrc
# 修改ZSH_THEME为agnoster
ZSH_THEME="agnoster"
-
-

默认:ZSH_THEME=“robbyrussell”

-
-

辅助

-
    -
  • 高亮插件
    cd ~/.oh-my-zsh/custom/plugins/
    git clone https://github.com/zsh-users/zsh-syntax-highlighting.git
    vim ~/.zshrc
    # 添加zsh-syntax-highlighting到plugins中,放在git后面
    plugins=(
    git
    zsh-syntax-highlighting
    )
    # 文件最后添加,然后保存并退出
    source ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
    -最后,对配置文件进行生效处理
    source ~/.zshrc
    -
  • -
  • 命令补全
    -安装步骤和上面的高亮插件一致
    cd ~/.oh-my-zsh/custom/plugins/

    git clone https://github.com/zsh-users/zsh-autosuggestions

    vi ~/.zshrc
    -
  • -
  • 设置背景图
    -iTerm2 -> Preferences -> Profiles -> Window -> BackGround Image
  • -
-

主题选择

-

Oh my zsh 本身也包含了很多主题,而我比较喜欢的一款 powerlevel10k 的主题,非常的简洁,满足我的装 X 的需求的同时,极其简单的设置也是我对其爱不释手。

-

之前最早使用的是 powerlevel9k,我的博客里面一些终端的截图就是 powerlevel9k,但随着我的需求越来越多以及加载的插件也越来越多,我不能忍受 iTerm2 在使用 时的效率问题,然后就看到了powerlevel10k,它是在powerlevel9k 的基础上迭代的,大大提高了响应效率和更加简洁的配置等

-

安装

-

官方提供了多种安装方式(Manual,Oh My Zsh,Homebrew 等等),选择你熟系的方式进行安装,我这里使用 Oh My Zsh 方式安装,选择了 gitee 镜像地址进行下载

-
# 1. 下载主题
git clone --depth=1 https://gitee.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
# 2. 在 .zshrc 文件中设置主题
vim ~/.zshrc
# 3. 大概 19 行,设置 ZSH_THEME 参数的值为 powerlevel10k/powerlevel10k
ZSH_THEME="powerlevel10k/powerlevel10k"
-
-

默认安装路径:/Users/<PC USER NAME>/.oh-my-zsh/custom/themes/powerlevel10k

-
-

更新

-

当初选择的什么安装方式,就使用什么方式进行更新,我这里以 Oh My Zsh 方式进行更新

-
git -C ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k pull
-

配置

-

只需要在终端()或者 iTerm2 命令窗口中输入 p10k configure 即可,然后根据提示选择即可完成配置,我比较懒,就用的 Pure 样式

-

问题

-

VS Code

-

在 VS Code Terminal 中显示不出来图标或者显示乱码异常等问题,先设置 VS Code 字体看看是否能解决,如果不能再进行排查

-

Open File → Preferences → Settings, 在搜索框中输入terminal.integrated,字体设置为MesloLGS NF.

-

常用工具

-

Homebrew

-
    -
  • Homebrew 是一款自由及开放源代码的软件包管理系统,用以简化macOS系统上的软件安装过程,最初由马克斯·霍威尔(Max Howell)写成
  • -
  • Homebrew Cask,它是一套建立在 Homebrew 基础之上软件安装命令行工具,是 Homebrew 的扩展
  • -
-

官方

-
-

如果你使用的官方安装教程,需要切换 brew 的镜像源,可参考 专治各种网络不服 文章

-
-
安装
-

在你的终端(Terminal/iTerm2)运行如下脚本

-
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
-
卸载
-
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"
-

国内(推荐)

-
-

按照命令行上面的提示,进行安装

-
-
安装
-
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
-
卸载
-
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/HomebrewUninstall.sh)"
-

常用命令

-

对于 brew 和 brew cask 的命令基本一致,比如

-
    -
  • 【brew】:brew install [包名]
  • -
  • 【brew cask】:brew cask install [包名]
  • -
-
brew 基础命令
-
# 安装指定包应用,如:brew install wget
brew install [包名]
# 重新安装指定应用,如:brew reinstall wget
brew reinstall [包名]
# 卸载指定包,如:brew uninstall wget
brew uninstall [包名]
# 列出已安装的软件
brew list
# 全部更新过时软件,如:brew upgrade
brew upgrade
# 指定更新过时的软件,如:brew upgrade wget
brew upgrade [包名]
# 用浏览器打开brew的官方网站
brew home
# 显示当前软件信息,如:brew info wget
brew info [包名]
# 显示包依赖,如:brew deps wget
brew deps [包名]
# 清理所有包的旧版本
brew cleanup
# 清理指定包的旧版本
brew cleanup [包名]
# 查看可清理的旧版本包,不执行实际操作
brew cleanup -n
# 查找指定包
brew search [包名]
# 查看缓存路径
brew --cache
-
brew service
-
# 查看使用brew安装的服务列表
brew services list
# 启动服务(仅启动不注册)
brew services run formula|--all
# 启动服务,并注册
brew services start formula|--all
# 停止服务,并取消注册
brew services stop formula|--all
# 重启服务,并注册
brew services restart formula|--all
# 清除已卸载应用的无用的配置
brew services cleanup
-

Nmap

-

端口扫描必备工具

-
# 安装 nmap 工具
brew install nmap
-

nmap

-

常用快捷键

-

标签

-
    -
  1. 新建标签:⌘(command) + t
  2. -
  3. 关闭标签:⌘(command) + w
  4. -
  5. 切换标签:⌘(command) + 数字 ⌘(command) + 左右方向键
  6. -
  7. 切换全屏:⌘(command) + enter
  8. -
  9. 查找:⌘(command) + f
  10. -
-

分屏

-
    -
  1. 垂直分屏:⌘(command) + d
  2. -
  3. 水平分屏:⌘(command) + ⇧(shift) + d
  4. -
  5. 切换屏幕:⌘(command) + ⌥(option) + 方向键 ⌘(command) + [ 或 ⌘(command) + tab 所在的数字]
  6. -
  7. 查看历史命令:⌘(command) + ;
  8. -
  9. 查看剪贴板历史:⌘(command) + ⇧(shift) + h
  10. -
-

其他

-
    -
  1. 清除当前行:⌘(command) + u
  2. -
  3. 清屏:⌘(command) + r
  4. -
  5. 列出剪切板历史:⌘(command) + ⇧(shift) + h
  6. -
  7. 命令快照(很实用的一个功能):⌘(command) + ⌥(option) + b -
    -

    大概只能记录 30s 左右的操作

    -
    -
  8. -
-

常见问题

-

文本乱码

-

在一开始使用macOS就已经安装iTerm2来代替了系统自带的Terminal应用,毕竟颜值是决定要不用长期使用的重要因素

-

iTerm2对应的配置文件:.zshrc,Terminal对于的配置文件:.bash_profile.bashrc

-
    -
  • 问题:iTerm2查看本地文件,能正常显示,无乱码,但查看服务器上文件,出现乱码
  • -
  • 原因:本地iTerm2终端和服务器字符集不一致,造成乱码,macOS默认Terminal应用是utf-8,而iTerm2默认没有设置utf-8编码
  • -
  • 解决办法:给本地的.zshrc设置字符集编码
    # 使用vim打开.zshrc文件
    vim ~/.zshrc
    # 在文本内容末尾添加以下两行内容进行字符编码设置
    export LC_ALL=en_US.UTF-8
    export LANG=en_US.UTF-8
    # 保存文件内容,退出vim模式,并使刚刚设置的内容生效
    source ~/.zshrc
    -
    -

    帮助:可以在本地和服务器上分别使用locale命令来查看,本地和服务器的字符编码是否一致

    -
    -
  • -
-

结束指定进程

-
# 查看指定端口号 lsof -i:端口号
lsof -i:8088
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 72612 blade 18u IPv6 0x21ccddb0352361e5 0t0 TCP *:radan-http (LISTEN)
# kill指定进程
kill -9 72612
-

免密登录服务器

-

一图胜千言,请看图

-

ssh-login

-

代理处理

-

在 Mac 系统上,使用 iTerm2 是一件很享受的过程,很多事情都可以通过命令行直接完成,但是一个致命的问题是,很多连接在国内环境下,异常忙,比如通过命令 clone 或处理 GitHub 上的项目,速度慢的让人抓狂,虽然电脑开启了代理(非全局),但视乎没有什么作用,针对此问题,需要让我们的终端也通过代理

-
    -
  1. install privoxy
    brew install privoxy
    -
  2. -
  3. setting privoxy
    vim /usr/local/etc/privoxy/config
    -
  4. -
  5. config privoxy
    listen-address 0.0.0.0:xxxx
    forward-socks5 / localhost:1080 .
    -
    -

    0.0.0.0 可以让其他设备访问到,若不需要,请修改成用 127.0.0.1;xxxx是HTTP代理的默认端口;
    -localhost:1080 是 SOCKS5(shadowsocks) 默认的地址,可根据需要自行修改,且注意不要忘了最后有一个空格和点号。

    -
    -
  6. -
  7. start privoxy
    # 因没有安装在系统目录内,所以启动的时候需要打全路径
    sudo /usr/local/sbin/privoxy /usr/local/etc/privoxy/config
    # 查看是否启动成功(1087 端口号换成自己的)
    netstat -na | grep 1087
    # 看到有类似如下信息就表示启动成功了
    tcp4 0 0 *.1087 *.* LISTEN
    -代理端口查看
    -shadowsocks-proxy
  8. -
  9. use proxy -
      -
    • temp proxy
      -如果关闭终端标签页或窗口,功能就会失效 -
        -
      • star proxy
        # 这里的端口号1087,换成你自己的
        export http_proxy='http://localhost:1087'
        export https_proxy='http://localhost:1087'
        -
      • -
      • cancel proxy
        unset http_proxy
        unset https_proxy

        -
      • -
      -
    • -
    • auto proxy -
        -
      • setting ~/.bash_profile
        # 打开.bash_profile 文件
        vim ~/.bash_profile
        # .bash_profile文件最后添加(1087 端口替换成你自己的)
        export http_proxy='http://localhost:1087'
        export https_proxy='http://localhost:1087'
        # 保存文件 :wq 后,使配置生效
        source ~/.bash_profile
        -
      • -
      • 上面的方式也可以在文件(.bash_profile)中加入如下方法,使用时只需要在终端中输入proxy_on命令,关闭输入proxy_off
        function proxy_off(){
        unset http_proxy
        unset https_proxy
        echo -e "已关闭代理"
        }

        function proxy_on() {
        export no_proxy="localhost,127.0.0.1,localaddress,.localdomain.com"
        export http_proxy="http://127.0.0.1:1087"
        export https_proxy=$http_proxy
        echo -e "已开启代理"
        }
        -
      • -
      -
    • -
    -
  10. -
  11. test
    # 已废弃,只能查看到当前的 IP 地址,其他信息请使用 curl cip.cc 命令
    curl ip.gs
    -proxy-config
    curl cip.cc
    -proxy-config-info
  12. -
-

参考

-
    -
  • Mac终端-iTerm2使用
  • -
  • Homebrew国内如何自动安装(国内地址)
  • -
  • iTerm2 用法与技巧
  • -
-]]>
- - macOS - - - iTerm2 - -
- - MacBook Pro 疑难杂症 - /2020/11/13/mac-question/ - 这是一篇记录使用macOS系统时遇到的一些疑难杂症

-

macOS Big Sur

-

在 2020.11.13 正式推送了 macOS Big Sur version 11.0.1 版本,这一个版本是改动比较大的版本,这里关于它的新特性就不做介绍了,有兴趣的请查看官方网站介绍 Big Sur

-

- -

Glance 失效

-

Glance 是一个快速预览增强,可以对一些文件进行快速预览,大大提高我们的日常效率,但该应用在 Big Sur 版本中不兼容,由于作者已入职 Apple,且对项目做了归档,不在维护,因此该问题依旧没有解决,可以使用一个付费的应用iPreView来满足当前需要

-
-

Glance 在 Big Sur 系统中失效

-
-

AirPods 异常

-

在 AirPods 使用过程中,发现有时候耳机并不能正常工作。通常情况下,我会断开与 macOS 的连接,重新连接,如果还是不能正常工作,在 macOS 的系统蓝牙设置里面,移除连接的耳机设备,将耳机放入 AirPods 盒子里面,先盖上盒子,然后再打开盒子,此时并按住 AirPods 盒子背后的按钮,直到前面呼吸灯变成白色,然后再 macOS 的蓝牙里面找到新的设备,并连接配对。同时也可参考官方指引步骤 连接并使用 AirPods 和 AirPods Pro

-

单耳工作

-

换一个连接设备,检查耳机是否正常,如果是正常,那说明耳机没有问题,问题就出在 macOS 声音管理上面,打开系统设置 -> 声音 -> 输出模式 ->设置为居中的平衡模式(既双耳工作)

-

airpods-settings

-]]>
- - macOS - - - Exp - iTerm2 - -
- - 品·杭州 - /2018/04/29/memory-hz1/ - 上有天堂,下游苏杭,杭州,一个温文尔雅,一个记忆中天堂,一个南方姑娘的城市。
-杭州:毕业后的第二个城市,很开心在这样的城市生活,工作,结识这里的人,杭州和家乡的气候非常相似,因此在杭州有种在家的感觉,在这里遇到的的人,我都会记着你们美丽帅气的脸庞

- -

18年是一个动荡的一年,曾经的伙伴渐渐的离开了的团队,这两年中,有的人毕业,有的人结婚,有的人生子,有的人成长,感谢我能成为你们生命中的一个过客,和你们一起经历生活百态

-

不管你们在何方,从事着什么样的工作,过着什么样的生活,我会想你们,愿你们的一切顺利

-

粗略的剪影,请异步优酷

-

不遵守规则的人,我们叫他废物,但是,不珍惜同伴的人,连废物都不如
-——宁智波·带土

-
]]>
- - Memory - - - 杭州 - -
- - 微服务架构 - Alibaba 生态整合(一) - /2020/11/11/microservices-alibaba1/ - 曾几何时,市面上对于微服务,分两个派系,一个派系以阿里为主的 Dubbo 生态体系,还有一派以 Spring Cloud 生态为主的体系,这两个系列的讨论也一直没有停息过。但现在 Spring Cloud Alibaba 的出现,提供了一整套构建分布式应用开发的微服务组件,由于这些组件是构建在原生的 Spring Cloud 之上,因此其服务治理方面的能力可认为是 Spring Cloud Plus, 不仅完全覆盖 Spring Cloud 原生特性,而且提供更为稳定和成熟的实现。那么从本系列就开始跟着我一起用阿里系的应用搭建分布式微服务应用,满足企业级的应用需要,而不是停留在 Dome 级别的应用框架使用。废话不多说,我们一起开始这一系列的实践

- -

本篇文章主要讲一讲在构建分布式微服务应用时,经常遇到的问题以及对于同类型组件选择,以及在开发过程中相关问题的思考,对于在整个应用开发过程中,开发人员应该怎么去配合等等,那第一个问题是面对我们的业务场景该如何去做技术选型,我们先看 Spring 官方经典的微服务架构图

-

-

微服务的核心组件由:网关,服务注册发现,服务配置,熔断限流等组成

-

注意这里微服务主要以 Alibaba 系相关的开源组件为基础构建,并非是 Spring Cloud Alibaba 项目的照搬,而是基于企业实际业务需求的抽象整合,只为提高效率、总结编程套路以及提升编程思想

-
-

选型

-
    -
  • 编程语言:Oracle JDK 8
  • -
  • 构建工具:Gradle
  • -
  • 网关路由:Spring Cloud Gateway
  • -
  • 服务通信:Dubbo
  • -
  • 消息管理:RockerMQ
  • -
  • 分布式事务:Seata
  • -
  • 注册中心及配置中心:Nacos
  • -
  • 限流,熔断,降级:Sentinel
  • -
  • 文档管理:SpringFox + Knife4j + Dubbo-Api-Docs
  • -
  • 部署发布:Docker + Nexus Repository OSS
  • -
  • 运维监控:Prometheus + Grafana
  • -
-

-

SpringCloud VS SpringCloud Alibaba

-

这里我汇总到表格中,方便查看比较

-

-

相关问题

-

JDK

-

OpenJDK

-

Java 最早由 SUN(Sun Microsystems,发起于美国斯坦福大学,SUN 是 Stanford University Network 的缩写)发明,2006 年 SUN 公司将 Java 开源,此时的 JDK 即为 OpenJDK

-

OpenJDK 是 Java SE 的开源实现,由 SUN 和 Java 社区提供支持,2009 年 Oracle 收购了 SUN 公司,自此 Java 的维护方之一的 SUN 也就变成了 Oracle

-

大多数 JDK 都是在 OpenJDK 的基础上编写实现的,比如 IBM J9,Azul Zulu,Azul Zing 和 Oracle JDK。几乎所有的 JDK 都派生自 OpenJDK,他们之间不同的是授权许可证。常见的 OpenJDK 发行商

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
发行商长期支持(TLS)许可证(license)TCK 测试未修改的上游构建提供商业支持
AdoptOpenJDKYesYesNoOptionalOptional(IBM)
Alibaba DragonwellYesYesYesNoNo
Amazon CorrettoYesYesYesNoOptional
(on AWS)
Azul ZuluYesYesYesNoOptional
BellSoft Liberica JDKYesYesYesNoOptional
IBM Java JDKYesNoYesNoYes
ojdkbuildYesYesNoYesNo
OpenLogic OpenJDKYesYesNoNoOptional
Oracle Java SEYesNoYesNoYes
Oracle OpenJDKNoYesYesYesNo
Red Hat OpenJDKYesYesYesNoYes
SAP SAPMachineYesYesYesNoNo
-
-

TLS:long-term support,长期支持(LTS)是一种产品生命周期管理策略,在该策略中,与标准版相比,计算机软件的稳定版本可以维持更长的时间。该术语通常保留给开源软件,它描述的软件版本比该软件的标准版本支持数月或数年的支持。
-TCK:Technology Compatibility Kit,技术兼容性套件(TCK)是一套测试套件,至少名义上检查Java规范请求(JSR)的特定声称实施是否符合要求

-
-

OralceJDK

-

显而易见 OracleJDK 是在 Oracle 收购 SUN 公司之后,基于 OpenJDK 源码构建的 JDK 被命名了 OracleJDK,两则之间没有重大的技术差异

-

两者的区别

-

问题

-

有人会说了,这有啥好说的,我们在公司开发都是用 OracleJDK 的。曾经我也以为这两个区别不是很大,看公司的使用情况了,直到我使用了 CentOS 7 系统默认带的 OpenJDK 来编译 Gradle 项目,死活是编译不过,总是提醒我找不到 tools.jar 包。有图有真相

-

-

一开始,我把以为是环境配置的问题,但是经过一番折腾,卸载了自带的 OpenJDK,然后再用 yum install java 命令去安装 OpenJDK,发现并不是环境的问题,而是系统自带的这个 OpenJDK 是 JRE,所以并没有包含 tools.jar 文件。所以这个问题就是你系统 JDK 的问题了。建议卸载 JRE,重新安装 JDK

-
# 可以先查找 JDK,下面命令是我查找 java-1.8 的相关应用
yum search java-1.8 | grep -i --color JDK
# 也可以直接安装 JDK,比如我这里提供的 java-1.8.0-openjdk-devel.x86_64
yum install java-1.8.0-openjdk-devel.x86_64
-

-

Gradle or Maven

-

关于如何使用 Gradle 构建项目,以及使用 Gradle 配置符合企业敏捷开发需求,可查看我的 Gradle 系列的文章

-

jar 与 bootJar

-

之前在《SpringBoot(二) 启动分析JarLauncher》文章中进行对 SpringBoot 应用启动做了分析,提到了 jar 规范,做了简单的介绍,那么本篇在此基础上进一步的完善这个知识点

-
-

这里以 rc-microservices-alibaba 项目的 microservices-alibaba-gateway 模块的编译为例

-
-

jar

-

jar(Java Archive)可以看做是特殊文件压缩的一种,通常用于聚合大量的 Java 类文件,相关的元数据和资源文件到一个文件,以便分发 Java 平台应用软件或库。jar 文件是一种归档文件,以 ZIP 格式构建,以 .jar 为文件扩展名。包含一个可选的 META-INF 目录,可以通过命令行 jar 工具或使用 Java 平台中的 java.util.jar API 创建 jar 文件

-

可以看到,我们打包成 jar 的文件,仅仅是源码+资源文件,以及生成的 META_INF 文件

-
microservices-alibaba-gateway-1.0-SNAPSHOT
├── META-INF
├── org
│ └── incoder
│ └── gateway
│ ├── config
│ ├── exception
│ └── filter
├── static
└── templates
-

bootJar

-

看名字就知道,这是 SpringBoot 的专属 jar。为什么会有这种 jar,原因是在 SpringBoot 出现之前,我们的 jar 应用想要运行,需要将应用放入到 Tomcat 中。而 SpringBoot 的出现改变了这层关系,是 SpringBoot 在打包成 bootJar 时,会内置 Tomcat,我们可以直接运行启动 jar 应用,可能有人会说,这怎么改变了,不都还是运行在 Tomcat 上么。没错它确实依然运行在 Tomcat 上,但是他们的加载方式改变了

-

我们可以看到,打成 bootJar 的文件,除了 META-INF 相关文件,并且包含了 BOOT-INF 的 lib 路径下存放项目所使用的所有第三方的 jar 包 ,同时在打包的根目录,生成了 SpringBoot 的 loader 相关的文件

-
microservices-alibaba-gateway-1.0-SNAPSHOT
├── BOOT-INF
│ ├── classes
│ │ ├── META-INF
│ │ ├── org
│ │ │ └── incoder
│ │ │ └── gateway
│ │ │ ├── config
│ │ │ ├── exception
│ │ │ └── filter
│ │ ├── static
│ │ └── templates
│ └── lib
├── META-INF
└── org
└── springframework
└── boot
└── loader
├── archive
├── data
├── jar
├── jarmode
└── util
-

Spring 生态

-
    -
  1. Spring:一个一站式轻量级Java 开发框架,核心是控制反转(IOC)和面向切面(AOP),针对开发 Web 层,业务层,持久层等提供了多种配置解决方案,也是整个微服务开发的基石
  2. -
  3. SpringMVC:是 Spring 基础之上的一个 MVC 框架,主要处理 Web 开发的路径映射和视图渲染,属于 Spring 框架中 Web 层开发的一部分(开发配置非常繁琐,复杂)
  4. -
  5. SpringBoot:专注于服务方面的接口开发,和前端解耦,默认优于配置,一定程度上取消了 XML 配置,是一套快速开发的脚手架,能快速开发单个微服务
  6. -
  7. SpringCloud:大部分功能组件基于 SpringBoot 去实现,提供了完整的微服务架构的技术生态,SpringCloud 专注于微服务的整合和管理
  8. -
-

单工程 or 聚合工程

-
-

个人推荐单工程的方式,毕竟聚合工程最终会随着业务的发展推进,需要拆分为单项目开发管理,那还不如一开始就拆分

-
-

单工程

-

这里的单工程是指,每一个模块都是一个项目,由一个仓库进行管理,特点及要求如下

-
    -
  1. 适合团队小组分工明确,开发人员多
  2. -
  3. 适合项目迭代快
  4. -
  5. 需要比较健全的基础设施,比如网关,公共基础工具包,消息管理,以及自动化部署相关服务设施
  6. -
-
-

相关的构架过程可参考 Gradle(三)SpringBoot 单工程 文章

-
-

聚合工程

-

这里的聚合工程是指,将整个系统开发的所有模块以及公共模块都放在一个项目工程中,也就是用同一个仓库来进行管理,特点如下

-
    -
  1. 适合项目初期,项目分工不是特别明确,开发人员少
  2. -
  3. 项目需要集中管理
  4. -
-
-

相关的构架过程可参考 Gradle(四)SpringBoot 聚合工程 文章

-
-

业务拆分

-

对于服务的拆分是没有统一的标准,除了通过实际的业务场景,团队能力,人员组织架构等多种因素综合考虑。都根据实际的需求进行调整,对于拆分主要从以下原则去思考

-
    -
  1. 单一职责原则:保证每个服务只做好一件事,体现“高内聚,低耦合”,尽量减少对外界环境的依赖
  2. -
  3. 服务依赖原则:避免服务间的循环依赖,在设计时就需要对服务进行分级,区分核心服务与非核心服务
  4. -
  5. Two Pizza Team原则:让团队保持在2 个披萨就能让队员吃饱的小规模概念
  6. -
-

参考

-
    -
  • 传统行业转型微服务的挖坑与填坑
  • -
  • Java官方(Oracle/Sun)发布的JDK,和开源项目OpenJDK,里面包含的JVM是否相同
  • -
  • OpenJDK和Oracle JDK有什么区别和联系?
  • -
-]]>
- - Microservices - - - Microservices - Alibaba - -
- - 微服务架构 - SpringCloud 生态整合(一) - /2020/11/08/microservices-springcloud1/ - 微服务这一概念在 2014 年的 3 月份随着 James Lewis 和 Martin Fowler 在博客中对于微服务这一概念做出了详细的阐述,开始走进开发者的视野。在 Spring 官方的加持下,助推微服务架构风格的应用开始火边整个后端领域。在早起微服务化的演进中 Netflix 的一些列开源的组件,迅速占领微服务生态中的 C 位,提供了网关路由,负载均衡,服务注册发现,服务通信,服务熔断限流等核心组件

- -

本系列文章是基于 Spring 官方提供的组件,完成相关功能组件的整合,以满足企业需求为目的的学习总结。本篇文章先从 Spring 官网经典的微服务架构图开始

-

-

注意这里主要以 Spring 系官方相关的开源组件为基础构建,并非是 Spring Cloud 项目的照搬,而是基于企业实际业务需求的抽象整合,只为提高效率、总结编程套路以及提升编程思想

-
-

当然这只是我在实际生产实践中的总结,并不一定适合你的业务场景,你也可以参考我的另一个系列 Alibaba 生态整合,希望能对你有所帮助

-

选型

-
    -
  • 编程语言:Oracle JDK 8
  • -
  • 构建工具:Gradle
  • -
  • 网关路由:Spring Cloud Gateway
  • -
  • 服务通信:OpenFeign
  • -
  • 注册中心:Eureka
  • -
  • 配置中心:Spring Cloud Config
  • -
  • 限流,熔断,降级:Hystrix
  • -
  • 文档管理:SpringFox + Knife4j
  • -
  • 部署发布:Docker + Nexus Repository OSS
  • -
  • 链路追踪:Spring Cloud Sleuth + Zipkin
  • -
-]]>
- - Microservices - - - Microservices - SpringCloud - -
- - 【译】• 微服务 - /2019/06/01/microservices/ - 这是第一篇翻译文章,用于学习近些年火热的微服务,这篇是微服务概念是由 James Lewis 所著,虽然官网已有中文翻译,但是在学习过程中,应该应该动手输出,这样有助于对知识的理解和记忆,废话不多说,开始翻译

-

微服务

-
-

近些年术语“微服务架构”就像雨后春笋般蓬勃的发展,微服务描述软件应用设计是独立可部署服务一个特殊方式。虽然这些都不够准确的去定义一个架构风格,但存在一些通用的特质(大家达成共识的特征),如何去组织围绕业务能力,如何自动化部署,端点的智能发现,以及语言和数据去中心化的控制

-
- -

James Lewis

-

James Lewis 是 ThoughtWorks 的首席顾问,也是技术顾问委员会的成员。James 利用小型协作服务构建应用程序的兴趣起源于大规模集成企业系统的背景。他构建数量级的系统都使用微服务,并且几年来,他一直积极参与不断地社区发展

-

Martin Fowler

-

Martin Fowler 是一个作者,演讲家,和普通的软件开发,他一直对如何组件化软件系统的问题感到困惑,他希望微服务能够实现其倡导者所发现的早期承诺

-
-

“微服务”在当时任然是一个新的名词。虽然我们的自然倾向是通过这些构建,这个技术分隔软件系统,这个术语描述了一种我们发现越来越有吸引力的软件系统风格。我们已经看到很多项目在过去的几年中使用这种风格,到目前为止的结果是积极的,以至于对于我们的许多同事而言,这已成为构建企业级应用的默认样式。然而,遗憾的是,没有太多信息可以描述微服务的风格以及微服务是如何实现

-

简而言之,微服务架构风格[1]是一个开发单应用作为小型服务套件开发模式,每个应用运行在自己的进程中并且他们之间通过轻量级的机制进行通信,通常的如 HTTP 资源 API。这些服务围绕业务能力并且这些都是可以独立的自动化部署。这些服务我们进行去中心化的集中管理。这些服务可以使用不同的编程语言来编写,同样也可以使用不同的数据存储技术

-

开始解释微服务风格,将它与单体风格进行比较是有用的:作为单元构建的单片应用程序。企业应用程序通常由 3 个之上主要构成部分:

-
    -
  • 客户端(由用户机器上的浏览器中运行的 HTML 页面和 JavaScript 组成)
  • -
  • 数据库(由插入到公共中的许多表组成,通常是关系型,数据库管理系统)
  • -
  • 服务端应用程序。
    -这个服务端应用程序处理 HTTP 请求,执行域逻辑,从数据库中检索和更新数据,并选择装配发送到浏览器的 HTML 视图。这个服务端应用程序是一个单体的,一个逻辑可执行文件[2] ,任何一次更改都生成一个新的版本去构建和部署
  • -
-

这种单体服务是构建这种系统的自然方式。所有的请求逻辑处理都运行在一个进程中,允许你使用语言的基本特性将应用划分为类,功能和命名空间。对于一些其他样例,你可以运行和测试应用在开发者的笔记本上,并使用部署管道确保已正确测试并部署到生成环境中。你可以通过负载均衡运行许多实例来进行水平扩展(常见单体应用模式 前面通过Nginx负载均衡,在 Nginx 后面运行多个应用实例)

-

单体应用程序可以成功,但是越来越多的人感到沮丧-特别是随着更多应用程序部署到云端。更改周期紧密相连-对应用程序的一小部分更改,需要重新构建和部署整个应用。随着时间的推移,通常很难保持良好的模块化结构,是的更难以保持改变只影响模块中的一个模块的更改。扩展需要扩展整个应用程序,而不是需要更多资源的部分扩展

-

图 1:单体应用和微服务
-从上图可知单体应用和微服务在部署的角度(可升缩角度)来讲:

-
    -
  • 单体应用:进行可升缩,是将单体应用整个进行升缩,每台机器上的应用都是相同的
  • -
  • 微服务:每个服务都是独立的单元,可根据需要对服务单元进行任意组合进行升缩,每台机器上的应用是不相同的
  • -
-

这些挫折导致了微服务架构的风格:构建应用程序作为服务套件。事实上服务是独立部署和可扩展的,每个服务之间也提供坚实模块的边界,甚至允许不同的编程语言编写不同的服务。它们也可以由不同的团队来管理

-

我们并不认为微服务风格是新颖的或创新的,其根源可以归结为 Unix 的设计原则。但是我们认为这些没有足够的人考虑微服务架构风格,如果使用它们,许多软件的开发会更好,从中获益匪浅

-

微服务架构的特征

-

我们不能说微服务架构风格有正式的定义,但我们可以尝试描述我们认为合适标签的架构的共同特性。与概述共同特征的任何定义一样,并非所有的微服务架构都具备所有的特征,但我们确实希望大多数微服务架构都具有大多数的特性。虽然我们的作者一直是这个相当宽松社区的积极成员,我们的目的是尝试描述我们在自己的工作中所看到的以及我们所知道的团队的努力,特别是我们没有规定一些符合的定义

-

服务组件化

-

只要我们参与软件行业,人们一直希望通过将组件集成在一起来构建系统,我们在物质世界中看待事物的方式有很多类似,在过去的几十年中,我们已经看到了大多数语言平台的大型公共 libraries 的大量进展

-

在谈论组件时,我们遇到了组件构成的困难定义,我们的定义组件是一个可独立更换和升级的软件单元

-

微服务架构会使用到这些 libraries,但他们讲自己的软件组件化的主要方式是分解为服务。我们定义 libraries 作为组件链接到程序中,也可以使用内存函数中调用的组件,而服务是进程外的组件,它们与诸如 Web 服务请求或远程调用之类的机制进行通信。(这与许多 OOP[3] 中的服务对象感念不同)

-
-

所谓的库都是调用在同一个进程当中,而服务的调用是跨进程的,要通过 Web 请求的方式或者是 RPC 的方式进行通信

-
-

将服务用作组件(而不是 libraries)的一个主要原因是服务可以独立部署。如果你在单个进程中有多个 libraries组成的应用程序[4],则对任何单个组件的更改都会导致必须重新部署整个应用程序。但如果一个应用由多个服务组成,你可以期望任何单服务的改变仅需要更新自己。这不是绝对的,一些更改改变了部分服务接口,从而导致一定的协调,但良好的微服务架构的目标是通过服务合同中的内聚服务边界和演化机制来最小化这些架构

-

将服务用作组件的另一个结果是更明确的组件接口,多数语言没有很好的机制来定义已发布的接口。通常这并不仅仅只有文档和原则性问题,来防止客户破坏组件的封装原则,而且会导致组件间过度紧密耦合。通过使用显示远程调用机制,服务可以更容易地避免这种情况

-

使用这种服务也有一些缺点。远程调用通常要比进程内调用成本要高,因此远程调用需要更粗粒度的,这通常更难以使用。如果你需要去更改组件间的职责分配,那么当你跨越流程边界时,这种行为的变化就更难

-

在第一次中,我们可以观察到服务可以映射到运行时的进程,但这只是一个大致的描述。一个服务可能包含多个进程,这些进程始终一起开发和部署,这样的应用进程和数据库是这个服务所独有的

-

围绕业务能力进行组织

-

在寻找将大型应用程序拆分为多个部分时,通常管理侧重于技术层,导致 UI 团队,服务器逻辑团队和数据库团队。当团队按照这些方式分开时,即使是简单的更改也可能导致跨团队项目需要时间和预算批准。一个聪明的团队围绕这个进行优化,并未减少这两个情况的发生——会强制将逻辑放置到可以访问的应用中。换句话说,逻辑无处不在。这就是康威定律[5] 的一个例子

-
-

Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure
-——Melvyn Conway, 1967

-
-

Conway's Law in action
-微服务划分方法是不同的,分为围绕业务能力组织的服务。此类服务为该业务领域采用广泛的软件实现,包括用户页面,持久存储,以及任何额外协作。因此,团队是跨职能的,包括开发所需的全部技能:用户体验,数据库和项目管理

-

Service boundaries reinforced by team boundaries

-
-

微服务有多大?
-虽然“微服务”已经成为这种架构风格的流行名称,但它的名字确实导致了对微服务的不关注以及关于什么构成“micro”的争论。在我们与微服务从业者的对话中,我们看到了一系列服务规模。报道的最大数量遵循亚马逊的 Two Pizza Team 的概念(比如:整个团队都可以讨厌两个披萨),意味着不超过十二人。对于规模较小的服务,我们已经看到一个6人的团队在支持6个服务。

-

这导致了这样的问题:在这个尺寸范围内是否存在足够大的差异,每个人的服务和每个服务的尺寸不应该集中在一个微服务的标签下。目前我们认为将它们组合在一起会更好,但当我们进一步探索这种风格时,我们肯定会改变主意

-
-

以这种方式组建的一家公司是 www.comparethemarket.com。跨职能团队负责构建和运营每个产品,每个产品分为多个通过消息总线进行通信的单独服务。

-

大型单机应用程序也可以围绕业务功能进行模块化,尽管这不是常见的情况。当然,我们会敦促一个庞大的团队构建一个单体的应用,以便在业务线上划分自己。我们在这里看到的主要问题是,它们往往围绕太多的背景进行组织。如果整体跨许多这些模块化边界,那么团队中的个体成员很难将它们适应其短期组织中。此外,我们看到模块化生产线需要大量的规范来执行。服务组件所需要的更明确的分离是的更容易保持团队边界清晰

-

产品不是项目

-

我们看到的大多数应用程序开发工作都使用项目模型:其目的是提供一些软件然后被认为是完成的。完成后,软件将移交给维护组织,构建他的项目团队将被解散。

-

微服务支持者倾向于避免这种模式,而是倾向于认为团队应该在其整个生命周期内拥有产品。对此的一个共同启示是亚马逊的概念“你构建,运行它”,开发团队对生产中的软件负全部责任。这使的开发人员能够日常接触他们的软件在生成中的行为,并增加与用户的联系,因此他们必须承担至少一些支持工作。

-

产品心态,与业务能力的联系紧密相连。不是将软件视为一组要完成的功能,而是存在一种持续的关系,其中的问题是软件如何帮助其用户增强业务能力

-

没有理由不采用单一应用程序采用相同的方法,但较小的服务粒度可以更容易地在服务开发人员和用户之间创建个人关系

-

智能端点和哑的 pips

-

在构建不同进程间通信结构时,我们已经看到许多产品和方法都强调将重要的smarts放入沟通机制本身。一个很好的例子是企业服务总线(ESB),其中 ESB 产品通常包括用于消息路由,编排,转换和应用业务规则的复杂工具。

-
-

微服务和 SOA
-当我们谈到微服务时,一个常见的问题,这是否是我们十年前看到的面向服务的体系结构(SOA),这一点是有道理的,因为微服务风格非常类似于 SOA 的一些拥护者所支持的。然而,问题在于 SOA 意味着太多不同的东西,并且大多数时候我们遇到称为“SOA”的东西,它与我们在这里描述的样式有很大不同,通常是由于专注于用于集成单片应用程序的 ESB

-

特别是我们已经看到了许多拙劣的服务导向实现——从隐藏 ESB[6] 中的复杂性的趋势,失败的多年计划,耗资数百万美元,没有任何价值,积极治理模式,积极抑制变化,有时很难看到过去的这些问题

-

当然,微服务社区中使用的许多技术都是从开发人员在大型组织中集成服务的经验中发展而来的。容忍读者模式就是一个例子。使用网络努力做出了贡献,使用简单的协议是从这些经验中得到的另一种方法——远离中心标准的反应,这种标准已达到复杂性,坦率地说,令人叹为观止(只要你需要一个本体来管理你的本体,你就知道你遇到了很大的麻烦)

-

SOA 的这种场景表现导致一些服务提倡者完全拒绝 SOA 标签,尽管其他人认为服务是 SOA 的一种形式,也许正确的服务向导,无论哪种方式,SOA[7] 意味着这些不同的事物意味着有一个更清晰地定义这种建筑风格的术语是有价值的

-
-

微服务社区倾向于采用另一种方法:智能端点和愚蠢的 pips。从微服务构建的应用程序旨在尽可能地分离和聚集——他们拥有自己的域逻辑,在经典的 Unix 意义上更像是过滤器——接收请求,适当地应用逻辑并产生响应。这些是使用简单的 RESTish 协议而不是复杂的协议(如 WS-Choregoraphy 或 BPEL 中央工具的编排)编排的。

-

最常用的两个协议是 HTTP 请求——响应资源 API 和轻量级消息[8] 传递。第一个最好的表达方式是

-
-

Be of the web, not behind the wed
-– lan Robinson

-
-

微服务团队使用万维网(在很大程度上,Unix)构建的原则和协议。经常使用的资源可以通过开发人员或操作人员的非常小的努力来缓存。

-

常用的第二种方法是通过轻量级消息总线进行消息传递。选择的基础设施通常是哑的(哑仅作为消息路由器的行为)—— 向 RabbitMQ 或者 ZeroMQ 这样的简单实现不仅仅提供可靠的异步结构——智能功能存在于那些生产和消费诸多消息的各个端点中,即存在于各个服务中。

-

在一个单体应用中,组件在进程中执行,它们之间通信是通过方法调用或函数调用。将整体变为微服务的最大问题在于改变通信模式。从内存中方法调用到 RPC 的简单转换导致繁琐的通信,这种通信效果不佳。相反,您需要粗粒度的方法替换细粒度的通信。

-

去中心化的治理

-

集中治理的后果之一是在单个技术平台上实现标准化的趋势。经验表明,这种方法是有限的——不是每个平台是一样的,不是每个平台的解决方案是一致的。我们推荐使用正确的工具来完成工作,而单体应用程序在一定程度上利用不同的语言,但这并不常见

-

将单个应用组件拆分为多个服务,我们可以在构建每个组件时做出选择。你希望使用 Node.js 建立一个简单的报告页面?没问题。想通过 C++ 来实现出彩的实时组件?没毛病。想换不同风格的数据库,以更好地适应一个组件的读取行为?可以重建

-

当然,只是因为你可以做某件事,并不意味着你可以应该——但以这种方式划分你的系统意味着你可以选择

-

构建微服务的团队也更喜欢采用不同的标准方法。他们更倾向于其他开发人员可以使用的有用工具来解决与他们面临的类似问题,而不是使用在纸上某处写下的一组定义标准。这些工具通常从实现中收集并广泛的共享,有时,但不仅仅是使用内部开源模型。现在 Git 和 GitHub 已经成为事实上的版本控制系统,开源实践在内部变得越来越普遍。

-

Netflix 是遵循这一理念的组织的一个很好的例子。共享有用的,尤其是经过实战考验的代码,因为鼓励其他开发人员以类似的方式解决类似问题,但如果需要,可以选择不同的方法。共享库往往侧重于数据存储,进程间通信的常见问题,我们将在下面进一步讨论基础架构自动化

-

对于微服务社区来说,管理费用特别缺乏吸引力。这并不是说社区不重视服务契约。恰恰相反,因为往往会有更多。只是他们正在寻找管理这些契约的不同方式。像容错读取消费者驱动的契约这样的模式通常应用于微服务。这些援助服务契约独立发展。在构建过程中执行消费者驱动的契约可以增强信心,并提供有关您的服务是否正常运行的快速反馈。事实上,我们知道澳大利亚的一个团队通过消费者驱动的契约推动服务的建设。他们使用简单的工具来定义合同服务。在编写新服务的代码之前,这将成为自动构建的一部分。然后,该服务仅构建在满足合同的程度——在构建新软件时避免“YAGNI”[9] 困境的优雅方法。这些技术和围绕他们成长的工具通过减少服务间的时间耦合来限制重要合同管理的需要

-
-

多语言,多选择
-JVM 作为平台的发展只是在一个通用平台中混合语言的最新例子。近十年依赖,通常的做法是采用更高级别的语言来更高级别的抽象。同样,在平台底层以更低层次的编程语言编写性能敏感的代码也很普遍。然而,许多单块系统并不需要这种级别的性能优化,另外 DSL 和更高层次的抽象也不常用(这令我们感到失望)。相反,许多单体应用通常就使用单一编程语言,并且对所用的技术数量进行限制的趋势[10]

-
-

也许去中心化治理的最高点就是建立它/运行它,由亚马逊推广的精神。团队负责他们构建的软件的所有方面,包括全天候运行软件。这种责任水平的下放绝对不是常态,但我们确实看到越来越多的公司将责任推向开发团队。Netflix 是另一个采用这种精神[11] 的组织。每天晚上凌晨 3 点您被你的寻呼机唤醒,无疑是在编写代码时专注于质量的强大动力。这些想法与传统的集中治理模式相差甚远

-

去中心化数据管理

-

去中心化数据管理以多种不同的方式呈现。在最抽象的层面上,它意味着世界的概念模型在不同系统之间会有所不同。在整个大型企业时,这是一个常见问题,客户的销售视角将与支持视角不同。从销售视角中称为“客户”的某些内容,可能根本不会出现在支持视角中。那些在两个视角中具有相同属性的事物,或许在语义上有微妙的不同

-
-

经过实战检验的标准和强制执行的标准
-微服务团队倾向于避开企业架构小组制定的严格执行标准,但很乐意使用甚至宣传 HTTP,ATOM 和其他微格式等开放标准的使用,这有点很二分法

-

关键的区别在于如何制定标准以及如何实施标准。有 IETF 等团体管理的标准只有在更广泛的世界中有多高实施时才能成为标准,并且通常来至于成功的开源项目

-

这些标准与企业的许多标准不同,后者通常由最近没有编程或受供应商过度影响的团体开发

-
-

此问题在应用程序间很常见,但也可能在应用程序中发生,特别是将应用程序划分为单独的组件时。一种有用的思考方式是“领域驱动设计”中的“限定上下文”的概念。DDD 将复杂领域划分为多个限界上下文,并映射出他们之间的关系。此过程对单体和微服务架构两者都很有用,而且就像前面有关“业务功能”一节中所讨论的那样,在服务和各个限界上下文之间所存在的自然的联动关系,能有助于澄清和强化这种划分。

-

除了关于概念模型的分散决策之外,微服务还分散了数据存储决策。虽然单一应用程序更喜欢使用单个逻辑数据库来存储持久性数据,但企业通常更喜欢在一系列应用程序中使用单个数据库——其中许多决策是通过供应商的商业模式来实现。微服务更喜欢让每个服务管理自己的数据库,可以是同一数据库技术的不同实例,也可以是完全不同的数据库系统——这种方法称为"Polyglot Persistence"。你可以在整体中使用多语言持久性,但它在微服务中更常出现。
-

-

跨服务分散数据责任对管理更新有影响。处理更新的常用方法是在更新多个资源时使用事务来保证一致性。这种方法通常用于整体结构中。

-

使用这样的事务有助于保持一致性,但会产生显著的时间耦合,在多个服务中是有问题。众所周知,分布式事务很难实现,因此微服务架构强调服务之间的无事务协调,明确承认一致性可能只有最终的一致性,而问题则通过补偿操作来处理。

-

选择以这种方式管理不一致是许多开发团队面临的新挑战,但它通常与业务实践相匹配。企业通常会处理一定程度的不一致,以便快速响应需求,同时采取某种逆转流程来应对错误。只要修复错误的成本低于在更大的一致性下丢失业务的成本,那么权衡是值得的。

-

基建设施自动化

-

基础设施自动糊技术在过去几年中发生了巨大变化——特别是云和 AWS 的发展降低了构建,部署和运行微服务的操作复杂性。

-

许多使用微服务构建的产品或系统都是由具有丰富的持续交付(Continuous Delivery)经验的团队构建的,并且是前身的持续集成(Continuous Integration)。以这种方式构建软件的团队广泛使用基础设施自动化技术。这在下面显示的构建管道中说明

-

basic build pipeline

-

由于这不是关于持续交付的文章,我们将在这里引起注意几个关键功能。我们希望尽可能地信心使我们的软件正常工作,因此我们进行了大量的自动化测试。推广工作软件“向上”管道意味着我们自动化部署到每个新环境。

-
-

做正取的事情很容易
-我们发现由于持续交付和部署而增加自动化的一个副作用是创建有用的工具来帮助开发人员和操作人员。用于创建人工制品,管理代码库,提供简单服务或添加标准监视器和日志记录的工具现在非常普遍。网上最好的例子可能是 Netflix 的开源工具集,但还有其他一些,包括我们官方使用的 Dropwizard

-
-

一个单一的应用程序将非常愉快地构建,测试和推动通过这些环境。事实证明,一旦你投资自动化整个生产的生产之路,那么部署更多地应用程序视乎不再那么可怕。请记住,CD的目标之一就是使用部署无聊,所以无论是一个还是多个应用,只要它任然无聊就无聊无所谓[12]

-

我们看到团队使用广泛的基础设施自动化的另一个领域是管理生产中的微服务。与我们上面的断言相反,只要部署很无聊,单块和微服务之间没有太大的区别,每个部署的运营环境可能会截然不同
-Module deployment often differs

-

容错设计

-

使用服务作为组件的结果是,应用需要设计以便他们能够容忍服务的失败。由于提供者不可用(不可达)等,任何服务调用都可能失败,客户端必须尽可能优雅地对此作出响应。与单体设计相比,这是一个缺点,因为它引入了额外的复杂性来处理它。结果是微服务团队持续不断反思服务失败如何影响用户体验。Netflix 的 Simian Army 在工作日引发服务甚至数据中心的故障,以测试应用程序的弹性和监控。

-
-

断路器和“可随时上线的代码”
-断路器一词与其他一些模式一起出现发布,如 Bulkhead 和 Timeout。在构建彼此通信的应用系统时,将这些模式加以综合运用变得至关重要。Netflix 博客这篇文章很好的解释了这些模式如何应用。

-
-

这种在生产环境中所进行自动化测试,足以让大多数运维组织兴奋地浑身颤栗,就像在一周的长假即将到来前那样。这并不是说单体式架构风格不具备复杂的监控设置——在我们的经验中,这在单体系统中并不常见罢了。

-

由于服务可能随时发生故障,因此能够快速检测故障并在可能情况下自动恢复服务非常重要。微服务应用程序非常重视应用程序的实时监控,检查“架构元素指标”(数据库每秒获得多少请求)和“业务相关指标”(例如每分钟收到多少订单)。当系统某个地方出现问题,语义监控可以提供一个预警,从而触发开发团队跟进和调查工作。

-

这对于微服务架构尤为重要,因为微服务对编排和事件协作的偏好会导致紧急行为。虽然许多权威人士赞扬偶然出现的价值,但事实是,新兴行为有时可能是一件坏事。监控对于快速发现下不良紧急行为至关重要,因此可以修复。

-
-

“同步调用”有害
-每当您在服务之间进行多次同步调用时,您将遇到停机的乘法效应。简而言之,就是系统停机时间成为各个组件停机时间的产物。您面临一个选择,使您的呼叫异步或管理停机时间。在 www.guardian.co.uk网站上,他们在新平台上实施了一条简单的规则——每个用户请求一次同步调用,而在 Netflix,他们的平台 API 重新设计已经在 API 结构中建立了异步性。

-
-

monoliths 可以像微服务一样透明——事实上,他们应该是。不同之处在于您绝对需要知道在不同进程中运行的服务何时断开连接。对于同一过程中的库,这种透明性不太可能有用。

-

微服务团队希望看到针对每个服务的复杂监控和日志记录设置,例如显示上/下状态的仪表板以及各种运营和业务相关指标。有关断路器状态,当前吞吐量和延迟的详细信息是我们经常遇到的其他示例。

-

“演进式”设计

-

微服务从业者通常拥有“演进式”设计背景,而且通常将服务分解视为额外的工具,使应用程序开发人员能够控制应用程序中的更改而不会减慢变更。变更控制并不一定意味着改变——通过正确的态度和工具,你可以对软件进行频繁,快速和良好控制的变更。

-

每当你尝试将软件系统分解为组件时,就面临着如何进行划分各个部分的决定——我们决定将应用程序切分的遵循的原则是什么?组件的关键属性是独立替换和可升级性[13] 的特点——这意味着需要寻找这些点,即想象在不影响其合作者的情况下重写组件。事实上,许多微服务团队通过明确预期服务将来会废弃,而不是守着这些服务做长期的演进。

-

Guardian 网站是一个设计和构建为单体的应用程序的一个很好例子,然而它已经开始向在微服务方向进行演进了。原先的单体系统依然是网站的核心,但在添加新特性时他们愿意以构建一些微服务的方式来进行添加,而这些微服务会去调用原先那个单体系统的 API。这种方法对于本质上是临时的功能尤其方便,例如报道体育赛事的专用页面。当使用快速开发语言时,像这样的网站就能被快速整合在一起,并在时间结束后删除。我们在金融机构看到了类似的做法,针对一个市场机会,添加新服务进来,并在几个月甚至几周后丢弃。

-

这种对可替换性的强调是模块化设计一般性原则的一个特例,即通过“演进式”模式推动模块化实现。大家都愿意将那些在同时发生变化[14] 的东西,放到同一个模块中。很少变化的部分,应该放在不同的服务中,以区别那些当前正在经历大量变动的部分。如果您发现需要同时反复变更的两个服务时,那就表明他们应该合并。

-

将组件放入服务中可以为更细粒度的发布计划添加机会。对于单体应用,任何更改都需要完整构建和部署整个应用程序。但是对使用微服务,您只需要重新部署您修改的服务。这可以简化并加快发布过程。缺点是:必须考虑当一个服务发生变化时,依赖它并对其进行消费的其他服务将无法工作。传统的集成方法是尝试使用版本控制来解决这个问题,但微服务领域中,大家更喜欢使用版本控制作为最后不得已的手段。我们可以通过将服务设计为对提供者变更,尽量能够容错来避免大量版本控制

-

未来的方向是“微服务”吗?

-

我们写这篇文章的主要目的是解释微服务的主要思想和原则。通过花时间来做到这一点,我们清楚地认为微服务架构风格是一个重要的想法——值得认真考虑企业应用程序。我们最近使用这种方式构建了几个系统,并了解其他团队已经使用并支持这种方法。

-

我们了解到那些在某种程度上做为这种架构风格的实践先驱包括:亚马逊,Netflix,GuardianUK Government Digital Servicerealeastate.com.aucomparethemarket.com。2013 年的技术大会圈子充满了各种各样的,正在转向可以归类为微服务的公司——包括 Travis CI。此外,有很多组织长期以来一直在做我们称为微服务的东西,但没有使用过这个名字(通常被标记为 SOA——尽管如我们所说,SOA 有许多互相矛盾的形式[15]

-

然而,尽管有这些积极的经验,但我们并不认为我们确信微服务是软件架构的未来发展方向。虽然到目前为止我们的经验与单体应用相比是积极的,但我们意识到没有足够的时间让我们做出充分的判断。

-

-
-

我们的同事 Sam Newman 在 2014 年的大部分时间都在撰写一本书,该书描述了我们构建微服务的经验。如果想进一步了解该主题,这应该是您的下一步

-
-

通常,您的架构决策的真正后果只有在开发它几年后才会明显。我们已经由带着强烈模块化愿望的优秀团队所做的一些项目,最终却构建出一个单体架构,并在几年内不断腐化。许多人认为微服务不太可能出现这种衰退,因为服务边界是明确的,很难随意捣乱。然而,对于那些开发时间足够长的各种系统,除非我们已经见识的足够多,否则我们无法真正评估微服务架构是如何成熟的。

-

人们可能会期望微服务成熟得很好。在组件化的任何努力中,成功取决于在组件中的适用程度。很难弄清楚组件边界的确切位置。“演进式”设计承认难以对边界进行正确定位,因此它将工作的重点放到了易于对边界进行重构之上。但是当您的组件是具有远程通信的服务时。那么重构比适用进程内库要困难的多。跨服务边界移动代码很困难,任何接口更改都需要在参与者之间协调,需要添加向后兼容性,测试变得更加复杂。

-

另一个问题是,如果组件没有干净利落地组成一个系统,那么您所做的就是将复杂性从组件内部转移到组件之间的连接。这样做的后果,不仅仅是移动复杂性,而是将其移动到一个不那么明确且难以控制的地方。当你在一个小而简单的组件内部查看时,人们很容易认为事情已经变得更好了,然而却忽略了服务之间的杂乱连接

-

最后,还有团队技能的因素。新技术往往被技术更加过硬的团队所采用。对于技术更加过硬的团队更更有效的一项技术,并不一定适用于技术略逊一筹的团队。我们已经看到很多不太熟练的团队构建混乱的单体架构,当微服务发生这种混乱时,会出现什么情况?这需要花时间来观察。一个糟糕的团队,总是会创建一个糟糕的系统——很难说微服务是减少了杂乱,还是让事情变得更糟。

-

我们听到一个合理的说法,不应该一上来就以微服务架构作为起点。相反,从单体应用开始,保持模块化。当单体系统出现问题时将其拆分为微服务。(虽然这个建议并不理想,但是好的进程内接口通常不是一个好的服务接口)

-

因此,我们谨慎乐观地写下这一点。到目前为止,我们已经看到了足够多的微服务风格,觉得它是一条值得走的路。我们无法确定最终会在哪里结束,但软件开发的挑战之一是您只能根据您当前必须提供的不完善信息作出决策。

-

参考

-

虽然这不是一个详尽的列表,但是它们是微服务从业者可以从中吸取灵感来源,或者是那些倡导的理念与本所述内容详实的一些资料

-

博客和在线文章

-
    -
  • Clemens Vasters’ blog on cloud at microsoft
  • -
  • David Morgantini’s introduction to the topic on his blog
  • -
  • 12 factor apps from Heroku
  • -
  • UK Government Digital Service design principles
  • -
  • Jimmy Nilsson’s blog and article on infoq about Cloud Chunk Computing
  • -
  • Alistair Cockburn on Hexagonal architectures
  • -
-

书籍

-
    -
  • Release it
  • -
  • Rest in practice
  • -
  • Web API Design (free ebook). Brian Mulloy, Apigee.
  • -
  • Enterprise Integration Patterns
  • -
  • Art of unix programming
  • -
  • Growing Object Oriented Software, Guided by Tests
  • -
  • The Modern Firm: Organizational Design for Performance and Growth
  • -
  • Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation
  • -
  • Domain-Driven Design: Tackling Complexity in the Heart of Software
  • -
-

简报

-
    -
  • Architecture without Architects. Erik Doernenburg
  • -
  • Does my bus look big in this?. Jim Webber and Martin Fowler, QCon 2008
  • -
  • Guerilla SOA. Jim Webber, 2006
  • -
  • Patterns of Effective Delivery.Daniel Terhorst-North, 2011.
  • -
  • Adrian Cockcroft’s slideshare channel.
  • -
  • Hydras and Hypermedia. Ian Robinson, JavaZone 2010
  • -
  • Justice will take a million intricate moves Leonard Richardson, Qcon 2008.
  • -
  • Java, the UNIX way. James Lewis, JavaZone 2012
  • -
  • Micro services architecture. Fred George, YOW! 2012
  • -
  • Democratising attention data at guardian.co.uk. Graham Tackley, GOTO Aarhus 2013
  • -
  • Functional Reactive Programming with RxJava. Ben Christensen, GOTO Aarhus 2013 (registration required).
  • -
  • Breaking the Monolith. Stefan Tilkov, May 2012.
  • -
-

论文

-
    -
  • L. Lamport,“The Implementation of Reliable Distributed Multiprocess Systems”, 1978
  • -
  • L. Lamport, R. Shostak, M. Pease,“The Byzantine Generals Problem”, 1982
  • -
  • R.T. Fielding, “Architectural Styles and the Design of Network-based Software Architectures”, 2000
  • -
  • E. A. Brewer, “Towards Robust Distributed Systems”, 2000
  • -
  • E. Brewer, “CAP Twelve Years Later: How the ‘Rules’ Have Changed”, 2012
  • -
-

总结

-

单体应用与微服务比较

-

单体应用

-

特性

-
    -
  • 调用方便,都是在一个进程内进行调用(针对于 Java 来说,就是运行在一个 JVM 上的应用)
  • -
  • 部署方式简单,
  • -
  • 事务处理方式可以容易处理
  • -
  • 由于都在一个进程内API 调用,不涉及网络的访问,因此出错的可能性要低很多
  • -
-

优缺点

-
    -
  • 优点 -
      -
    • 为人所熟知
    • -
    • 便于共享
    • -
    • 易于测试
    • -
    • 容易部署
    • -
    -
  • -
  • 缺点 -
      -
    • 复杂性逐渐变高
    • -
    • 技术债务逐渐上升
    • -
    • 部署速度逐渐变慢
    • -
    • 阻碍技术创新
    • -
    • 无法按需伸缩
    • -
    -
  • -
-

微服务

-

特性

-
    -
  • 每个微服务可独立运行在自己的进程里
  • -
  • 一系列独立运行的微服务共同构建起了这个系统
  • -
  • 每个服务为独立的业务开发,一个微服务一般玩某个特定的功能,比如:订单管理,用户管理等
  • -
  • 微服务之间通过一些轻量的通讯机制进行通信,比如通过 REST API 或者 RPC 的方式调用
  • -
-

优缺点

-
    -
  • 优点 -
      -
    • 易于开发和维护
    • -
    • 启动较快
    • -
    • 局部修改容易部署
    • -
    • 技术栈不受限
    • -
    • 按需伸缩
    • -
    • DevOps
    • -
    -
  • -
  • 缺点 -
      -
    • 运维复杂
    • -
    • 数据一致性问题
    • -
    • 集成测试复杂
    • -
    • 重复代码
    • -
    • 监控困难
    • -
    -
  • -
  • 挑战 -
      -
    • 运维要求较高
    • -
    • 分布式的复杂性
    • -
    • 接口调整成本高
    • -
    • 重复你劳动
    • -
    -
  • -
-

设计原则

-
    -
  • 单一职责原则
  • -
  • 服务自治原则
  • -
  • 轻量级通信原则
  • -
  • 接口明确原则
  • -
-

附录

-
    -
  • Microservices
  • -
  • 校验 • CeaserWang
  • -
-
-
-
    -
  1. The term “microservice” was discussed at a workshop of software architects near Venice in May, 2011 to describe what the participants saw as a common architectural style that many of them had been recently exploring. In May 2012, the same group decided on “microservices” as the most appropriate name. James presented some of these ideas as a case study in March 2012 at 33rd Degree in Krakow in Microservices - Java, the Unix Way as did Fred George about the same time. Adrian Cockcroft at Netflix, describing this approach as “fine grained SOA” was pioneering the style at web scale as were many of the others mentioned in this article - Joe Walnes, Daniel Terhorst-North, Evan Botcher and Graham Tackley. ↩︎

    -
  2. -
  3. The term monolith has been in use by the Unix community for some time. It appears in The Art of Unix Programming to describe systems that get too big. ↩︎

    -
  4. -
  5. Many object-oriented designers, including ourselves, use the term service object in the Domain-Driven Design sense for an object that carries out a significant process that isn’t tied to an entity. This is a different concept to how we’re using “service” in this article. Sadly the term service has both meanings and we have to live with the polyseme. ↩︎

    -
  6. -
  7. We consider an application to be a social construction that binds together a code base, group of functionality, and body of funding. ↩︎

    -
  8. -
  9. The original paper can be found on Melvyn Conway’s website here. ↩︎

    -
  10. -
  11. We can’t resist mentioning Jim Webber’s statement that ESB stands for “Egregious Spaghetti Box”. ↩︎

    -
  12. -
  13. Netflix makes the link explicit - until recently referring to their architectural style as fine-grained SOA. ↩︎

    -
  14. -
  15. At extremes of scale, organisations often move to binary protocols - protobufs for example. Systems using these still exhibit the characteristic of smart endpoints, dumb pipes - and trade off transparency for scale. Most web properties and certainly the vast majority of enterprises don’t need to make this tradeoff - transparency can be a big win. ↩︎

    -
  16. -
  17. “YAGNI” or “You Aren’t Going To Need It” is an XP principle and exhortation to not add features until you know you need them. ↩︎

    -
  18. -
  19. It’s a little disengenuous of us to claim that monoliths are single language - in order to build systems on todays web, you probably need to know JavaScript and XHTML, CSS, your server side language of choice, SQL and an ORM dialect. Hardly single language, but you know what we mean. ↩︎

    -
  20. -
  21. Adrian Cockcroft specifically mentions “developer self-service” and “Developers run what they wrote”(sic) in this excellent presentation delivered at Flowcon in November, 2013. ↩︎

    -
  22. -
  23. We are being a little disengenuous here. Obviously deploying more services, in more complex topologies is more difficult than deploying a single monolith. Fortunately, patterns reduce this complexity - investment in tooling is still a must though. ↩︎

    -
  24. -
  25. In fact, Daniel Terhorst-North refers to this style as Replaceable Component Architecture rather than microservices. Since this seems to talk to a subset of the characteristics we prefer the latter. ↩︎

    -
  26. -
  27. Kent Beck highlights this as one his design principles in Implementation Patterns. ↩︎

    -
  28. -
  29. And SOA is hardly the root of this history. I remember people saying “we’ve been doing this for years” when the SOA term appeared at the beginning of the century. One argument was that this style sees its roots as the way COBOL programs communicated via data files in the earliest days of enterprise computing. In another direction, one could argue that microservices are the same thing as the Erlang programming model, but applied to an enterprise application context. ↩︎

    -
  30. -
-
-]]>
- - Translation - SpringBoot - - - SpringBoot - -
- - 第 100 篇原创文章 - /2020/12/31/milepost1/ - 未曾想过,居然能写到第 100 篇文章。虽然大部分文章都是线性流水操作,但全部是自己经过实践的总结;虽然没有精彩的故事,但都是自己成长的思考;虽然有时一篇文章需要要长达一个多月的反复核对,但还是能默默坚持。只是这第 100 篇来的有点晚,断断续续大概有 3 年的时间,时间是个坏老头,把我给你写情话,悄悄的改成了谎话!

- -

回想起之前写文章主要是为了记录一些操作步骤和一些知识点,方便遇到类似问题,快速定位,解决问题。但随着文章的越写越多,包含的内容也越来越多,需要去了解的知识也越来越多,真的是有一种 “你知道的越多,你不知道的越多” 的感觉,这种感觉让我对待每个知识点都能有往深去深挖的动力,对每一个知识点用自己文字将它讲出来时有一种让我欲罢不能成就感,这或许就是上瘾吧

-

总结下第一个里程碑,主要是平时接触到领域算是一些入门级别的一些文章,以及一些比较粗浅的见闻,缺乏深层次的剖析和思考,这也是第二个里程碑首要做的事情,把每个接触到的知识点进行深挖,打通自己的技术栈。技术领域能不能走得远,很大程度上并不是你的技能宽度,而是深度,是在一个方向上的深耕,并且对于底层的技能也是要有足够的涉猎,只有这样就算是技术的花样任它怎么去变,你都能以不变应万变(透过现象找到本质);第二点是是对第一阶段内容完善补充;第三点就是打磨自己的语言表达能力,让文章更加的通俗易懂;第四点就是不能太拖拉,要保持高效的内容输出

-

这一阶段,对开源项目贡献评价最高的是 rap2-delos 项目了。努力在接下来的里程中,提高质量和参与度,争取早日在大型项目中做到 Committer

-

很感谢这一路走来,大伙对我的认可和期待以及赞赏,下一个里程碑我们见~

-

-]]>
- - Milepost - - - note - -
- - 《激战》 - /2018/10/03/movie-fierce/ - -

怕,你就会输一辈子

- - -

喜欢其中的一些台词,大伙共勉

-
    -
  • 其实,我每次上台都很怕的,不过每次我都会跟自己说,我能做到
  • -
  • 这场比赛我可能会跌倒,但我一定会站起来
  • -
  • 怕,你就会输一辈子
  • -
- -

自己的一些感触:
-其实很多时候,道理都懂,但却不能坚持下去,但这些道理都在自己生活中一点点的用生活感悟出来,那这些道理会更浓烈,更让人刻骨铭心

-
    -
  • 尊重和珍惜,那些愿意为你去花时间的人
  • -
  • 要和自己志同道合,有共同目标的伙伴去互相较劲
  • -
  • 从哪里跌倒就要从哪里爬起来
  • -
  • 一路跌跌撞撞走下去,中间的酸甜苦辣是最美的味道
  • -
-]]>
- - Movie - - - 激战 - -
- - MQ 系列 — RabbitMQ(一)环境搭建 - /2020/11/10/mq-rabbit1/ - ]]> - - MQ - - - RabbitMQ - - - - MQ 系列 — RocketMQ(一)环境搭建 - /2020/11/10/mq-rocket1/ - 本篇我们来看 MQ 系列的另一个广泛使用的中间件 RocketMQ。官方介绍到 “Apache RocketMQ™ 是一个统一的消息传递引擎,轻量级的数据处理平台。Apache RocketMQ 是一个分布式消息传递和流媒体平台,具有低延迟,高性能和可靠性,万亿级容量和灵活的可伸缩性” 。更重要的是在分布式消息队列中,目前唯一提供完整的事务消息的,只有 RocketMQ。

- -
    -
  • RocketMQ 3.0.8 以及之前的版本是 支持分布式事务消息(找不到对应的提交记录)
  • -
  • RocketMQ 3.0.8 之后,分布式事务的阉割了,不支持分布式事务消息(找不到对应的提交记录)
  • -
  • RocketMQ 4.0.0 开始 Apache 孵化,但是也不支持分布式事务消息
  • -
  • RocketMQ 4.3.0 又开始支持分布式事务消息
  • -
-
-

基本概念

-

RocketMQ 由四部分组成:name servers, brokers, producers and consumers。它们中的每一个都可以在没有单个故障点的情况下进行水平扩展

-

name servers

-

用来保存 Broker 相关 Topic 等元信息并给 Producer,提供 Consumer 查找 Broker 信息。主要包括两个功能:

-
    -
  1. Broker 管理,NameServer 接受来自经纪人群集的注册,并提供心跳机制以检查经纪人是否还活着
  2. -
  3. Routing 管理,每个NameServer 将保存有关代理群集的完整路由信息以及客户端查询的队列信息
  4. -
-

brokers

-

负责消息的存储和传递,消息查询,HA 保证等(消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息)。Broker 服务器具有几个重要的子模块:

-
    -
  • Remoting Module:处理来自客户端的请求
  • -
  • Client Manager:管理客户(生产者/消费者)并维护消费者的主题订阅
  • -
  • Store Service:提供简单的 API,以在物理磁盘中存储或查询消息
  • -
  • HA Service:提供主代理(master broker)和从代理(slave broker)之间的数据同步功能
  • -
  • Index Service:通过指定的键为消息建立索引并提供快速的消息查询
  • -
-

producers

-

负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。支持分布式部署,分布式生产者通过多种负载平衡模式将消息发送到 Broker 集群。发送过程支持快速失败并且延迟低

-

consumers

-

负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序。支持 “推和拉” 模型中的分布式部署。它还支持集群使用和消息广播。它提供了实时消息订阅机制,可以满足大多数消费者的需求

-

整体流程

-

准备工作

-
    -
  • Linux
  • -
  • JDK8+
  • -
  • Maven3.2.x+
  • -
  • Git
  • -
-
-

相关工具没安装可参考 Linux 常用应用安装

-
-

单机部署

-

单机部署,主要是进行 RocketMQ 的简单使用,因此没有必要分配较大内存空间,RocketMQ NameServer 默认会占用 4G,因此在启动部署时会调整 JVM 的相关参数,指定分配内存空间

-

普通部署

-

RocketMQ 部署

-
    -
  1. Nameserver
    # 程序存放位置,根据喜好
    cd /home/application
    # 下载应用
    wget https://archive.apache.org/dist/rocketmq/4.7.1/rocketmq-all-4.7.1-bin-release.zip
    # 解压文件,并进入解压后的目录,进行查看目录概要等信息(没有 unzip 命令,请 yum install unzip)
    unzip rocketmq-all-4.7.1-bin-release.zip && cd rocketmq-all-4.7.1-bin-release/ && ls -l
    # 进入启动目录
    cd bin/

    # 编辑启动脚本文件,修个相应的 JVM 参数
    vim runserver.sh
    ### 定位到: JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=1 28m -XX:MaxMetaspaceSize=320m"
    ### 更改为: JAVA_OPT="${JAVA_OPT} -server -Xms512M -Xmx512M -Xmn256M -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"

    # 修个完成后启动 nameserver 应用
    nohup ./mqnamesrv &
    -
  2. -
  3. 启动 broker
    # 进入 bin 目录
    cd /home/application/rocketmq-all-4.7.1-bin-release/bin/

    # 编辑启动脚本文件,修个相应的 JVM 参数
    vim runbroker.sh
    ### 定位到: JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
    ### 更改为: JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn512m"

    # 修个完成后,后台启动 broker,-n 指定 NameServer 服务ip地址
    nohup ./mqbroker -n localhost:9876 &
    -
  4. -
  5. 验证 RocketMQ
    # 使用 clusterList 命令来查看集群的状态
    sh /home/application/rocketmq-all-4.7.1-bin-release/bin/mqadmin clusterList -n 127.0.0.1:9876
    -
  6. -
-

RocketMQ-Console 部署

-

通过命令去操作 RocketMQ,其实是比较麻烦,没有图形化来的直观和方法。为此 RocketMQ 官方提供了一个运维管理界面 RokcetMQ-Console-Ng,用于对 RocketMQ 集群提供常用的运维功能

-
-

基于 SpringBoot 开发

-
-
wget https://github.com/apache/rocketmq-externals/archive/rocketmq-console-1.0.0.tar.gz
tar -xf rocketmq-console-1.0.0.tar.gz
# 重命名,为了方便后续操作
mv rocketmq-externals-rocketmq-console-1.0.0/rocketmq-console rocketmq-consoe
cd rocketmq-console

# 编辑配置文件
vim src/main/resources/applications.properties
### 修改指向的 nameserver 地址
### rocketmq.config.namesrvAddr=127.0.0.1:9876

# 使用 maven 命令编译源代码
mvn clean package -DskipTests
# 复制包到自己常用的软件安装目录
cp rocketmq-console-ng-1.0.0.jar /opt/application/
# 启动 rocketmq-conolse
nohup java -jar rocketmq-console-ng-1.0.0.jar &
-

正常启动后,访问:http://localhost:8080 查看是否安装成功

-

如果你使用的 root 用户启动 rocketmq, rocketmq-console 应用,那么他们的日志分别在

-
    -
  • rocketmq: /home/root/logs/rocketmqlogs/
  • -
  • rocketmq-console: /home/root/logs/consolelogs
  • -
-
-

Docker 部署

-

截止 2020-11-10,官方的镜像依然还是 4.6 版本,难道又是阿里没人维护的 KPI 🙄

-

RocketMQ-Docker

-

分布式部署

-

普通部署

-

Docker 部署

-

参考

-
    -
  1. 《Apache RocketMQ 从入门到实战》.pdf
  2. -
  3. 芋道 RocketMQ 极简入门
  4. -
  5. 芋道 Spring Boot 消息队列 RocketMQ 入门
  6. -
  7. RocketMQ 4.7.1 环境搭建、集群、SpringBoot整合MQ
  8. -
-]]>
- - MQ - - - RocketMQ - -
- - OSS 之 Minio 初体验 - /2021/03/16/minio/ - MinIO 是一个基于 Apache License v2.0 开源协议使用 Go 语言开发的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

- -

MinIO 包含 MinIO Server, MinIO Client 以及方便开发基于不同编程语言使用的 MinIO SDK,这三部分组成,使用步骤也很简单,在服务器上安装 MinIO Server 应用,在项目中集成对应的 MinIO SDK,然后按照你的业务情况编写相应的实现即可,在开始前,我们先看看为什么我选择 MiniIO 作为自建的 OSS 服务

-
    -
  1. MinIO 由良好的存储机制
  2. -
  3. 兼容 Amason 的 S3 分布式存储
  4. -
  5. 天然的支持云原生
  6. -
  7. 支持私有部署,可分布式,可单机,100%开源
  8. -
  9. 友好简单的部署方式,提供管理页面
  10. -
  11. 还可以配合其他的健康管理工具进行监控,比如 Prometheus
  12. -
-

安装

-

由于 MinIO Server 已经提供了 Docker 的安装镜像,那我们就以 Docker 安装为例,其他安装方式可参考官方教程 MinIO Quickstart Guide

-

关于 Docker 的安装这里不再赘述,Docker 相关详细的使用等知识,可参考我之前的文章 Docker(一)

-
# 1. 拉取 minio docker 镜像
docker pull minio/minio
# 2. 运行 minio 服务
docker run -p 9000:9000 --name minio \
-v /opt/docker/minio/data:/data \
-v /opt/docker/minio/config:/root/.minio \
-d --restart=always \
-d minio/minio server /data
-
-

这里简单说一下命令的含义,应用命名为 minio ,运行服务在 9000 端口,同时将容器的相关路径文件映射到宿主机的 /opt/docker/minio 路径,开机自启

-
-

成功运行服务,可查看日志

-
Endpoint: http://172.17.0.2:9000 http://127.0.0.1:9000
Browser Access:
http://172.17.0.2:9000 http://127.0.0.1:9000
Object API (Amazon S3 compatible):
Go: https://docs.min.io/docs/golang-client-quickstart-guide
Java: https://docs.min.io/docs/java-client-quickstart-guide
Python: https://docs.min.io/docs/python-client-quickstart-guide
JavaScript: https://docs.min.io/docs/javascript-client-quickstart-guide
.NET: https://docs.min.io/docs/dotnet-client-quickstart-guide
Detected default credentials 'minioadmin:minioadmin', please change the credentials immediately using 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD'
-

安装完成后,我们就可以通过 http://localhost:9000 访问 MinIO 服务,默认用户名和密码分别为: minioadmin, minioadmin

-

使用

-

页面操作

-

-

我们直接看图,输入账号密码后,可以看到 MinIO 的管理页面,我们就可以上传文件,是不是很方便。第一次上传必须先要创建一个 bucket 后,才可以上传,如下图操作结果

-

-

Client 操作

-

SDK 操作

-

这里以 Java 语言为例,查看官方文档时,一定要查看英文文档,中文文档已年久失修落后很多,其他的语言实现请参考官方文档

-
-

导入依赖

-
dependencies {
implementation "io.minio:minio:8.1.0"
}
-

功能实现

-

由于我这里是 SpringBoot 项目,为了方便在应用的 application.yml 文件中配置了 MinIO 相关的参数

-

配置文件

-
minio:
# minio 服务运行的地址
endpoint: http://127.0.0.1
# minio 服务运行的端口
port: 9000
# minio 服务登录账号
accessKey: minioadmin
# minio 服务登录密码
secretKey: minioadmin
# minio 设置上传默认存放桶
bucketName: cpe-manager-test
-

工具类

-
/**
* 资源上传工具类
*
* @author : Jerry xu
* @since : 2021/3/18 14:05
*/
@Slf4j
@Component
public class MinioUtils {

/**
* minio:
* endpoint: http://192.168.1.163
* port: 9000
* accessKey: minioadmin
* secretKey: minioadmin
* bucketName: cpe-manager-test
*/
@Value("${minio.endpoint}")
private static final String ENDPOINT = "http://192.168.1.163";
@Value("${minio.port}")
private static final Integer PORT = 19000;
@Value("${minio.accessKey}")
private static final String ACCESS_KEY = "minioadmin";
@Value("${minio.secretKey}")
private static final String SECRET_KEY = "minioadmin";
@Value("${minio.bucketName}")
private static final String BUCKET_NAME = "cpe-manager-test";

private static MinioClient minioClient;

public static MinioClient getInstance() {
if (minioClient == null) {
minioClient = MinioClient.builder().endpoint(ENDPOINT, PORT, false).credentials(ACCESS_KEY, SECRET_KEY).build();
}
return minioClient;
}

/**
* 获取minio所有的桶
*
* @return java.util.List<io.minio.messages.Bucket>
* @throws Exception exception
*/
public static List<Bucket> getAllBucket() throws Exception {
// 获取minio中所以的 bucket
List<Bucket> buckets = getInstance().listBuckets();
for (Bucket bucket : buckets) {
log.info("bucket 名称: {} bucket 创建时间: {}", bucket.name(), bucket.creationDate());
}
return buckets;
}

/**
* 将图片上传到minio服务器
*
* @param inputStream 输入流
* @param objectName 存储的文件名称,必须包含后缀
* @param bucketName 自定义存储桶
*/
public static String uploadToMinio(InputStream inputStream, String objectName, String bucketName) {
try {
// 获取文件后缀
String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf("."));
String contentType = FileType.getContentType(fileSuffix);
// // 重新生成文件名,避免重复
// String objectName = UUID.randomUUID().toString() + fileSuffix;
long size = inputStream.available();
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(inputStream, size, -1)
.contentType(contentType)
.build();
// 上传到minio
ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs);
inputStream.close();
if (!StringUtils.isEmpty(objectWriteResponse.etag())) {
// 返回上传获取到的地址
return getUrlByObjectName(objectName);
}
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 将图片上传到minio服务器,默认存放在 cpe-manager-test 桶内
*
* @param inputStream 输入流
* @param objectName 存储的文件名称,必须包含后缀
*/
public static String uploadToMinio(InputStream inputStream, String objectName) {
try {
// 获取文件后缀
String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf("."));
String contentType = FileType.getContentType(fileSuffix);
// // 重新生成文件名,避免重复
// String objectName = UUID.randomUUID().toString() + fileSuffix;
long size = inputStream.available();
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(objectName)
.stream(inputStream, size, -1)
.contentType(contentType)
.build();
// 上传到minio
ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs);
inputStream.close();
if (!StringUtils.isEmpty(objectWriteResponse.etag())) {
// 返回上传获取到的地址
return getUrlByObjectName(objectName);
}
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 根据指定的objectName获取下载链接,需要bucket设置可下载的策略
*
* @param objectName 对象的名称
* @return java.lang.String
*/
public static String getUrlByObjectName(String objectName) {
try {
return getInstance().getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(BUCKET_NAME)
.object(objectName)
// 过期策略【默认有效期7天】
// .expiry(2, TimeUnit.HOURS)
.build());
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 根据objectName从minio中下载文件到指定的目录
*
* @param objectName minio上的文件名称
* @param fileName 下载生成的文件名
* @param dir 文件目录
* @throws Exception exception
*/
public static void downloadFromMinioToFile(String objectName, String fileName, String dir) throws Exception {
GetObjectArgs objectArgs = GetObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(objectName)
.build();
File file = new File(dir);
if (!file.exists()) {
if (file.mkdirs()) {
log.error("创建失败");
}
}
InputStream inputStream = getInstance().getObject(objectArgs);
FileOutputStream outputStream = new FileOutputStream(new File(dir, fileName.substring(fileName.lastIndexOf("/") + 1)));
int length;
byte[] buffer = new byte[1024];
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
outputStream.close();
inputStream.close();
}

/**
* 根据文件名批量删除(默认删除 BUCKET_NAME 下的文件)
*
* @param listFile 文件名(含后缀)列表,例如:demo.png
* @return 成功返回为null, 失败返回Map<objectName, failMessage>
*/
@SneakyThrows
public static Map<String, String> removeObjects(List<String> listFile) {
List<DeleteObject> objects = new LinkedList<>();
Map<String, String> resultMap = new HashMap<>();
listFile.forEach(t -> objects.add(new DeleteObject(t)));
Iterable<Result<DeleteError>> results =
getInstance().removeObjects(
RemoveObjectsArgs.builder()
.bucket(BUCKET_NAME)
.objects(objects)
.build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
resultMap.put(error.objectName(), error.message());
log.error("Error in deleting:{}, message{}", error.objectName(), error.message());
}
return resultMap;
}

}
-

上传接口

-
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation(value = "文件上传", notes = "支持多文件上传")
public List<String> uploadTest(@ApiParam(value = "文件") @RequestParam("file") List<MultipartFile> file) {
// 上传的图片地址
List<String> successFile = new ArrayList<>(file.size());
file.forEach(t -> {
try {
String url = MinioUtils.uploadToMinio(t.getInputStream(), t.getOriginalFilename());
log.info("图片地址{}", url);
successFile.add(url);
} catch (IOException e) {
e.printStackTrace();
}
});
return successFile;
}

-

测试

-

问题

-

bucket命名

-

创建 bucket 时,命名不可以使用下划线符号 “_

-

账号密码修改

-

通过网页管理页面修改登录账号及密码,提示 “Credentials of this user cannot be updated through MinIO Browser.” ,原因是安装应用时,并未显示的指定用户名和密码,可在运行启动时添加如下配置

-
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \
-

最长7天有效

-

通过网页管理页面共享图片或者是使用 SDK 上传图片得到的图片 URL 地址,有效期最长为7天

-

mc config host add minio http://192.168.1.163:19000 minioadmin minioadmin --api S3v4
mc policy set public minio/cpe-manager-test

mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin --api S3v4
mc policy set public minio/bucket (bucket修改成你自己的名字)
-

图片无法查看

-
    -
  1. 使用 SDK 上传时,需要注意设置content-type信息
  2. -
  3. 无权限查看
  4. -
-

小结

-

关于 MinIO 还有很多知识点,本片只是站在使用者角度,把一些使用过程和问题进行了汇总,谈不上深度

-

参考

-
    -
  1. Minio 手册
  2. -
  3. Minio 示例
  4. -
  5. Minio 修改密码
  6. -
  7. Minio
  8. -
  9. Minio 安装以及使用
  10. -
  11. Minio 设置文件链接永久有效
  12. -
-]]>
- - Linux - - - FileUpdate - -
- - MySQL 必备技能 - /2019/11/01/mysql1/ -

- -

SQL 语句

-

SQL全称(Structured Query Language):是一种特定目的编程语言,用于管理关系数据库管理系统(RDBMS),或在关系流数据管理系统(RDSMS)中进行流处理
-也就是一种数据库查询和程序设计语言,用于存取数据以及查询和管理关系型数据库

-

SQL 规则

-
    -
  1. SQL 语句可以单行或多行书写,以分号 ; 结尾
  2. -
  3. 可以使用空格和缩进来增强语句可读性
  4. -
  5. MySQL数据库的 SQL 语句不区分大小写,关键字建议大写
  6. -
-

SQL 分类

-

SQL_Commands

-

DDL

-

Data Definition Language(DDL):数据定义语言,用来创建数据库中的表,索引,视图,存储过程,触发器等。

-
操作数据库
-
    -
  • CREATE:创建
    # 创建数据库
    CREATE DATABASE 数据库名称;
    # 创建数据库,判断是否存在,不存在则创建
    CREATE DATABASE IF NOT EXISTS 数据库名称;
    # 创建数据库并指定其字符集
    CREATE DATABASE 数据库名称 CHARACTER SET 字符集;
    -
  • -
  • ALERT:修改
    # 修改数据库的字符集
    ALTER DATABASE 数据库名称 CHARACTER SET 字符集名称;
    -
  • -
  • DROP:删除
    # 删除数据库
    DROP DATABASE 数据库名称;
    # 判断数据库存在,存在则删除
    DROP DATABASE IF EXISTS 数据库名称;
    -
  • -
  • 查询
    # 查询所有数据库的名称
    SHOW DATABASES;
    # 查询某个数据库的创建语句
    SHOW CREATE DATABASE 数据库名称;
    -
  • -
-
操作表
-
    -
  • CREATE:创建
    # 语法
    CREATE TABLE tableName(
    列名1 数据类型1,
    列名2 数据类型2,
    ....
    列名n 数据类型n,
    [添加约束...]
    );
    # 示例
    -
  • -
  • ALERT:修改
    # 修改表名
    ALTER TABLE 表名 RENAME TO 新表名;
    # 修改表的字符集
    ALTER TABLE 表名 CHARACTER SET 字符集名称;
    # 添加一列
    ALTER TABLE 表名 ADD 列名 数据类型;
    # 修改列名 类型
    ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
    # 只修改数据类型
    ALTER TABLE 表名 MODIFY 列名 新数据类型;
    # 删除列
    ALTER TABLE 表名 DROP 列名;
    -
  • -
  • DROP:删除
    # 删除表
    DROP TABLE 表名;
    # 判断表是否存在,存在则删除
    DROP TABLE IF EXISTS 表名;
    -
  • -
-

TRUNCATE 和 DELETE 区别

-
    -
  1. TRUNCATE TABLE 表名 语句在功能上与不带 WHERE 子句的 DELETE 语句相同;二者均删除表中的全部数据,但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少
  2. -
  3. DELETE 语句每次删除一行,并在事务日志中为所删除的每一行记录。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且旨在事务日志中记录页的释放
  4. -
  5. TRUNCATE TABLE 删除表中的所有行,但表结构及其列,约束,索引等保存不变,且会重置表的计数(通常我们作为表的主键);DELETE TABLE 不会重置计数;如果要删除表定义及其数据,使用 DROP TABLEE 语句
  6. -
-
-

DML

-

Data Manipulation Language(DML):数据操作语言,用来改变数据库数据

-
    -
  • -

    INSERT

    -
    # 插入
    INSERT INTO 表名(字段列表) VALUES(值列表)
    # 拷贝表
    INSERT INTO 新表名 SELECT * FROM 表名;
    CREATE TABLE 新表名 LIKE 表名;
    -
  • -
  • -

    UPDATE

    -
    UPDATE 表名 SET 字段1=1,字段n=值n [WHERE 条件] [ORDER BY 字段名 ASC|DESC] [LIMIT];
    -
  • -
  • -

    DELETE

    -
    DELETE FROM 表名 [WHERE 条件] [ORDER BY 字段名 ASC|DESC] [LIMIT];
    -
  • -
-

DQL

-

Data Query Language(DDL):数据查询语言,用于建立,修改,删除数据库中的各种对象

-
SELECT
column_1,column_2,...
FROM table_1
[INNER | LEFT |RIGHT] JOIN table_2 ON CONDITIONS
WHERE conditions
GROUP BY column_1
HAVING group_conditions
ORDER BY column limit offset,length
-

DCL

-

Data Control Language(DCL):数据控制语言

-
    -
  • GRANT
  • -
  • REVOKE
  • -
-

TCL

-

Transaction Control Language(TCL): 事务控制语言,用于维护数据的一致性

-

索引

-

字符集

-

常用命令

-

锁表处理

-

方法一

-
    -
  1. 查看是否锁表
    show OPEN TABLES where In_use > 0;
    -
  2. -
  3. 查看进程,查找被锁表的进程ID
    show processlist;
    -
  4. -
  5. kill 锁表的进程 ID
    kill id;
    -
  6. -
-

方法二

-
    -
  1. 查看当前数据库的锁表情况
    SELECT * FROM information_schema.INNODB_TRX;
    -
  2. -
  3. 杀掉查询结果中锁表的trx_mysql_thread_id
    kill trx_mysql_thread_id
    -
  4. -
-

MySQL 用户分配

-

mysql-account

-

参考

-
    -
  • 再见乱码:5分钟读懂MySQL字符集设置
  • -
-]]>
- - DataBase - MySQL - - - MySQL - -
- - Netty(四)之 gRPC - /2020/04/12/netty-grpc/ -

- -

gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

-

gPRC 是高性能,开源通用RPC框架,可以运行在任何环境中,它是可插拔的并且支持负载均衡,跟踪,运行状况检查和身份校验,从而有效地连接数据中心和跨数据中心的服务,它也适用于分布式计算的最后一段,以将设备,移动应用和浏览器连接到后端的服务

-

gRPC 可以使用 Protocol buffers 作为接口定义语言(IDL:Interface Definition Language)和基础的消息交换格式,通常来说,你可以使用 proto2 这个版本,但是我们建议你使用 proto3 这个版本和 gRPC 一起使用,支持完整的语言,同时避免客户端和服务端版本不一致出现的其他问题

-

gRPC

-

proto3 语言官方使用手册

-

proto3 指南

-

-

gRPC实践

-

gRPC下载

-

编译器安装

-

编写.proto 文件

-]]>
- - Netty - - - Netty - gRPC - -
- - Netty(二)之 Protobuf - /2019/11/29/netty-protobuf/ - Netty 框架中已经默认支持了 Protobuf 格式的数据传输,因此我们本节就来学习 Protobuf,Protobuf 主要用于进行 RPC 数据传输(它是一种自定义协议,这种协议能更好,更小体积,对数据编解码【序列号和反序列化的过程】),在学习 Protobuf 之前我们先了解两个概念 RMI 和 RPC

- -

RMI:Remote Method Invocation,用于跨机器方法调用,只针对于 Java(要求调用者和被调用者都必须是 Java 程序)

-
    -
  • client:stub(装)
  • -
  • server:skeleton(骨架)
    -client 与 server 底层通过 socket 数据传输
  • -
-

RPC:Remote Procedure Call,远程过程调用,原理和 RMI 一致,优势在于跨语言支持

-

那对于 RMI 和 RPC 编写的具体步骤如下:

-
    -
  1. 定义接口说明文件(IDL:Interface Description Language ):描述对象(结构体),对象成员,接口方法等一系列信息
  2. -
  3. 通过 RPC 框架所提供的编译器,将说明文件编译成具体语言文件
  4. -
  5. 在客户端与服务器端分别引入 RPC 编译器所生产的文件,即可享调用本地方法一样调用远程方法
  6. -
-

序列化与反序列化

-

序列化与反序列化也叫做,编码与解码

-

序列化:将对象转换成字节,这个过程是encode
-反序列化:将字节翻译成对象,这个过程是decode

-

Protobuf

-
    -
  • 官方网站:Protocol Buffers
  • -
  • 官方指南:Guide
  • -
  • 官方说明:Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.(Protocol buffers 是一种语言中立,平台中立,可扩展的一种机制用于序列化结构化的数据)
  • -
-

Protobuf 编译环境搭建

-
    -
  1. 下载对应系统的编译器,格式如 protoc-$VERSION-$PLATFORM.zip,这里下载的是 protoc-3.11.0-osx-x86_64.zip
  2. -
  3. 为了使用方便,我们需要将 protoc 解压的路径添加到环境变量中
  4. -
  5. 在终端中使用 protoc -h 命令验证 protoc 变量是否配置正确
  6. -
-

Protobuf 特定语言

-

这是一步可选步骤,根据自身需要,选择需要的语言编译文件,这里下载的是 protobuf-java-3.11.0.zip,用于学习了解 protoc 对 Java 编译的支持原理等

-

Protobuf 使用

-

在官方 README 介绍中,请查看Protobuf Runtime Installation说明,这里介绍了在使用不同语言时需要安装的一些依赖,比如这里查看 Java,在需要使用的项目中引入相关的依赖

-

简单使用

-
    -
  1. 编写.proto文件
    syntax = 'proto2';

    package org.incoder.protoc;

    option optimize_for = SPEED;
    option java_package = "org.incoder.protobuf";
    option java_outer_classname = "HelloProtobuf";


    message World {
    required string name = 1;
    optional string address = 3;
    }
    -
  2. -
  3. 执行编译命令,protoc --java_out=$DST_DIR $SRC_DIR/FILE_NAME.proto
  4. -
  5. 编写简单的测试,明白 RPC 的过程
    public static void main(String[] args) throws InvalidProtocolBufferException {
    ///////////////////////////////////////////////////////////////////////////
    // 把下面的这个过程等同到 RPC 的过程
    ///////////////////////////////////////////////////////////////////////////

    // A机器上构建了World对象
    HelloProtobuf.World world = HelloProtobuf.World.newBuilder()
    .setName("China")
    .setAddress("处于地球东半球")
    .build();

    // A 机器构建的对象转换成字节数组
    // 字节数组通过网络传输(Netty 等方式) A 机器传输到 B 机器
    byte[] world2ByteArray = world.toByteArray();

    // B 机器上把字节数转换成对象(取决于在 B 机器上的使用语言),并把数据打印出来
    HelloProtobuf.World worlds = HelloProtobuf.World.parseFrom(world2ByteArray);
    System.out.println(worlds);
    }
    -
  6. -
-

整个过程如下截图
-

-

在 Netty 中的应用(单消息)

-

和之前Netty初体验(一)中编写步骤一样,这里只是对Initializer 中使用Netty 提供相关 Protobuf 的工具类

-
    -
  • ProtobufDecoder:将收到的 ByteBuf 解码为 Google Protocol Buffers 和 MessageLite(),请注意,如果使用基于流的传输方式(比如:TCP/IP),则此解码器必须与适当的 ByteToMessageDecoder(如:ProtobufVarint32FrameDecoder 或者 ProtobufVarint32LengthFieldPrepender)
  • -
  • ProtobufDecoderNano:将接收到的 ByteBuf解码为 Google Protocol Buffers MessageNano,请注意,如果使用的是基于流的传输方式(如:TCP/IP),则此解码器必须与适当的 ByteToMessageDecoder(如果:LengthFieldBasedFrameDecoder)一起使用
  • -
  • ProtobufEncoder:将请求的 Google Protocol Buffers 和 MessageLite 编码为 ByteBuf
  • -
  • ProtobufEncoderNano:将请求的 Google Protocol Buffers MessageNano 编码为 ByteBuf
  • -
  • ProtobufVarint32FrameDecoder:解码器按消息中 Google Protocol Buffers 基于 128 Varints 整数长度字段的值动态拆分接收到的 ByteBuf
    For example:
    BEFORE DECODE (302 bytes) AFTER DECODE (300 bytes)
    +--------+---------------+ +---------------+
    | Length | Protobuf Data |----->| Protobuf Data |
    | 0xAC02 | (300 bytes) | | (300 bytes) |
    +--------+---------------+ +---------------+
    -
  • -
  • ProtobufVarint32LengthFieldPrepender:一种编码器,可在 Google Protocol Buffers Base 128 Varints 之前添加
    BEFORE ENCODE (300 bytes)       AFTER ENCODE (302 bytes)
    +---------------+ +--------+---------------+
    | Protobuf Data |-------------->| Length | Protobuf Data |
    | (300 bytes) | | 0xAC02 | (300 bytes) |
    +---------------+ +--------+---------------+
    -
  • -
-
SingleClient
-
public class SingleClient {

public static void main(String[] args) throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new SingleClientInitializer());

ChannelFuture channelFuture = bootstrap.connect("localhost", 5555).sync();
channelFuture.channel().closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
-
SingleClientInitializer
-
public class SingleClientInitializer extends ChannelInitializer<SocketChannel> {

@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(NettyDataInfo.Person.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());

pipeline.addLast(new SingleClientHandler());
}
}
-
SingleClientHandler
-
public class SingleClientHandler extends SimpleChannelInboundHandler<NettyDataInfo.Person> {

@Override
protected void channelRead0(ChannelHandlerContext ctx, NettyDataInfo.Person msg) throws Exception {

}

/**
* 客户端建立连接后发送消息给服务端
*
* @param ctx ctx
* @throws Exception exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
NettyDataInfo.Person person = NettyDataInfo.Person.newBuilder()
.setName("netty")
.setAge(20)
.setAddress("https://netty.io")
.build();

// 发送消息给服务器
ctx.channel().writeAndFlush(person);
}
}
-
SingleServer
-
public class SingleServer {

public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();

try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new SingleServerInitializer());

ChannelFuture channelFuture = bootstrap.bind(5555).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
}
}
}
-
SingleServerInitializer
-
public class SingleServerInitializer extends ChannelInitializer<SocketChannel> {

@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(NettyDataInfo.Person.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());

pipeline.addLast(new SingleServerHandler());
}
}

-
SingleServerHandler
-
public class SingleServerHandler extends SimpleChannelInboundHandler<NettyDataInfo.Person> {

@Override
protected void channelRead0(ChannelHandlerContext ctx, NettyDataInfo.Person msg) throws Exception {
// 打印客户端连接后发送的消息
System.out.println(msg.getName());
System.out.println(msg.getAge());
System.out.println(msg.getAddress());
}
}
-

在 Netty 中的应用(多消息)

-

由于通过 Netty(底层是 socket)使客户端与服务端建立连接,使用 Google Protocol Buffers 协议进行数据通信,而 ProtobufDecoder(MessageLite prototype) 需要指定具体的实例,因此想要进行多消息类型数据通信,可以有两种方式

-
    -
  1. 自定义通信协议
  2. -
  3. 在定义 IDL 时,将所有类型的数据进行定义,最终生成一个包含了通信所需的所有类型的顶层 Message
  4. -
-

方式二具体代码可参考:multiple

-

问题

-

环境搭建问题

-

在配置好环境变量后,执行 protoc -h 命令提示 “protoc” cannot be opened because the developer cannot be verified.
-

-
    -
  • 原因:在 macOS 10.15 版本上未授权访问
  • -
  • 解决:在系统设置中,进行授权,操作如下 Settings -> Security & Privacy -> General
    -
  • -
  • 验证:在终端中执行 protoc -h 命令
    -
  • -
-]]>
- - Netty - - - Netty - Protobuf - -
- - Netty(三)之 Thrift - /2019/12/01/netty-thrift/ - The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.

-

Apache Thrift软件框架,用于可扩展的跨语言服务开发,它包含软件栈和一个代码生成器用于构建服务,这个服务可以高效并且无缝的在 C++,Java,Python,PHP,Ruby,Erlang,Perl,Haskell,C#,Cocoa,Node.js,Smalltalk,OCaml 和 Delphi 等其他语言间协作

- -

Apache ThriftGoogle Protocal Buffers 都是一种可以用于在 Netty 之上的一种数据格式,Thrift 可应用的语言比 Protocal Buffers 多,并且 Thrift 除了用于传递数据的定义,底层还提供了传输层,因此可以单独的去使用,而不必强制运行在 Netty 载体之上

-

Thrift

-

Thrift数据类型

-

Thrift不支持无符号类型,因为很多编程语言不存在无符号类型,比如Java

-
    -
  • byte:有符号字节
  • -
  • i16:16位有符号整数
  • -
  • i32:32位有符号整数
  • -
  • i64:64位有符号整数
  • -
  • double:64位浮点数
  • -
  • string:字符串
  • -
  • bool:布尔值
  • -
-

Thrift容器类型

-
    -
  • list:一系列由T类型的数据组成有序列表,元素可以重复 -
    -

    集合中的元素可以是除了service之外的任何类型,包括exception

    -
    -
  • -
  • set:一系列由T类型的数据组成的无序列表,元素不可以重复
  • -
  • map:一个字典结构,key 为 K类型,value为 V类型,相当于Java中的 HashMap
  • -
-

Thrift支持的三类组件

-

struct

-

结构体,编译生成完成后,对应的是我们的类,就像 C 语言一样,thrift 支持 struct 类型,目的就是将一些数据聚合在一起,方便传输管理。struct 的定义形式如下:

-
struct People{
1:string name;
2:i32 age;
3:string gender;
}
-

枚举,枚举的定义形式和 Java 的 Enum 定义类似

-
enum Gender{
MALE,
FEMALE
}
-

exception

-

异常,客户端与服务端之间通信用到的接口可能抛出的异常,thrift 支持自定义 exception,规则与 struct 一样

-
exception RequestException{
1:i32 code;
2:string reason;
}
-

service

-

服务,客户端与服务端之间通信用到的接口,thrift 定义服务相当于 Java 中创建 interface 一样,创建的 service 经过代码生成命令之后就会生成客户端和服务端的框架代码

-
service HelloWorldService{
// service 中定义的函数,相当于 Java interface 中定义的方法
string doAction(1:string name, 2:i32 age);
}
-

类型定义

-

thrift 支持类似 C++ 一样的 typedef 定义,在定义完别名后,在后面的 IDL 文件中就可以使用别名进行编写

-
// 把 i32 别名成 int
typedef i32 int
// 把 i64 别名成 long
typedef i64 long
-

常量

-

thrift 也支持常量定义,使用 const 关键字

-
const i32 MAX_RETRIES_TIME = 10
const string MY_WEBSITE = "https://incoder.org"
-

命名空间

-

thrift 的命名空间相当于 Java 中的 package 的意思,主要目的是组织代码。thrift 使用关键字 namespace 定义命名空间

-
// 格式:namespace 语言名 路径
namespace java org.incoder.thrift
-

文件包含

-

thrift 也支持文件包含,相当于 C/C++ 中的 include,Java 中的 import,使用关键字 include 定义

-
include "global.thrift"
-

可选与必选

-

thrift 提供两个关键字requiredoptional,分别用于表示对应的字段是必填还是可选,主要根据你的业务来选择,推荐使用 optional

-
1:required string name;
2:optional i32 age;
-

Thrift工作原理

-

数据之间的传输使用socket(多种语言均支持),数据载以特定的格式(String等)发送,接收方进行语言解析。通过定义 Thrift 文件,由 Thrift 文件(IDL)生成双方语言的接口,model,在生成的model及接口中会有解析码,编码的代码

-

Thrift 实践

-

下载Thrift

-

这一步可以直接通过 maven 或者 gradle 的方式集成 Thrift 包到所需要的项目包管理中即可

-
# maven
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.13.0</version>
</dependency>

# gradle
compile 'org.apache.thrift:libthrift:0.13.0'
-

编译器安装

-

对于 macOS 可使用官方提供的方式去安装,也可以借助于 macOS 上,优秀的包管理工具 Homebrew 来进行安装,我这里就直接使用 Homebrew 进行安装,其他系统可参考官方文档

-
brew install thrift
-

编写.thrift文件

-

编写.thrift 文件的指南,可以参考官网文档,编写完文件,使用 thrift 编译器提供的命令生成相关的代码

-
thrift --gen <language> <Thrift filename>
-

示例

-
// 定义命名空间
namespace java org.incoder.thrift.java
namespace py org.incoder.thrift.py

// 定义别名
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String

// 定义 struct
struct Person{
1: optional String username,
2: optional int age,
3: optional boolean married
}

// 定义 exception
exception DataException{
1: optional String message,
2: optional String callStack,
3: optional String date
}

// 定义 service
service PersonService{
Person getPersonByUsername(1: required String username) throws (1: DataException dataException),

void savePerson(1: required Person person) throws (1: DataException dataException)
}
try:
person_handler = PersonHandler()
processor = PersonService.Processor(person_handler)

serverSocket = TSocket.TServerSocket(port=9090)
transportFactory = TTransport.TFramedTransportFactory()
protocolFactory = TCompactProtocol.TCompactProtocolFactory()

server = TServer.TSimpleServer(processor, serverSocket, transportFactory, protocolFactory)
server.serve()

except Thrift.TException as tx:
print(tx.message)
try:
tSocket = TSocket.TSocket('localhost', 9090)
tSocket.setTimeout(600)

transport = TTransport.TFramedTransport(tSocket)
protocol = TCompactProtocol.TCompactProtocol(transport)
client = PersonService.Client(protocol)

transport.open()
person = client.getPersonByUsername("张三")
print("username:" + person.username)
print("age:" + str(person.age))
print("married:" + str(person.married))

print("------------------------")
newPerson = ttypes.Person()
newPerson.username = "李四"
newPerson.age = 30
newPerson.married = True

client.savePerson(newPerson)
transport.close()
except Thrift.TException as tx:
print(tx.message)
public static void main(String[] args) throws Exception {
TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(9090);
THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4);
PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());

arg.protocolFactory(new TCompactProtocol.Factory());
arg.transportFactory(new TFramedTransport.Factory());
arg.processorFactory(new TProcessorFactory(processor));

TServer server = new THsHaServer(arg);
System.out.println("Thrift service Started!");
// 开启死循环
server.serve();
}
public static void main(String[] args) {
TTransport transport = new TFramedTransport(new TSocket("localhost", 9090), 600);
TProtocol protocol = new TCompactProtocol(transport);
PersonService.Client client = new PersonService.Client(protocol);

try {
transport.open();
// 调用定义通过用户名获取用户信息的接口方法 getPersonByUsername
Person person = client.getPersonByUsername("张三");
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());

System.out.println("-----------------------------");

// 调用定义保存用户信息的方法
Person per = new Person();
per.setUsername("李四");
per.setAge(30);
per.setMarried(true);
client.savePerson(per);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
transport.close();
}
}
-
-

相关源码rc-cluster-netty

-
-

其他

-

本地 Thrift 的 Python 环境,需要下载官方的文件,进行安装

-
# 方式一:
pip3 install thrift
# 方式二,下载官方包,进行安装
# 1. 下载文件
curl https://mirrors.tuna.tsinghua.edu.cn/apache/thrift/0.13.0/thrift-0.13.0.tar.gz
# 2. 解压文件
tar -zvxf thrift-0.13.0.tar.gz
# 3. 安装thrift
cd thrift-0.13.0/lib/py/
sudo python setup.py install
]]>
- - Netty - - - Netty - Thrift - -
- - Netty初体验(一) - /2019/11/20/netty/ - Netty 是国内外各大互联网公司的必备网络应用框架,Netty 主要处理与网络相关的一些应用。由于 Netty 设计的巧妙的实现方式,以及对协议很好的实现,使的 Netty 可以在各种应用场景下广泛的应用,无论是传统基于HTTP协议的访问方式,还是更底层基于socket的访问方式,以及支持HTML5规范中的websocket的长连接特性,都提供了比较好的支持

- -

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients(Netty 是一个异步的,事件驱动的网络应用框架,是可维护的,高性能的,协议化的服务端和客户端快速开发方式)

-

Netty 是一个非阻塞(NIO)客户端服务端框架,它可以快速的进行网络应用开发,例如:基于协议的客户端和服务端。它极大的简化并且支持流式的网络程序,例如:基于TCP和UDP的socket服务
-'快捷方便’并不意味着最终的应用程序会受到可维护性或性能问题的影响。Netty经过精心设计,具有实施许多协议所获得的经验,如FTP,SMTP,HTTP以及各种基于二进制和文本的遗留协议。因此,Netty 成功地找到了一种在不妥协的情况下实现易于开发,性能,稳定性和灵活性的方法

-

特点

-
    -
  • 适用于各种传输类型统一API - 阻塞和非阻塞 socket
  • -
  • 基于灵活且可扩展的事件模型,可以清晰地关注分离(separation of concerns)
  • -
  • 高度可定制的线程模型 - 单线程,一个或多个线程池,如:Staged Event Driven Architecture(SEDA,阶段型事件驱动架构,将一个请求分成若干个阶段,每个阶段可以根据自身情况不用数量的线程来分别进行处理,阶段与阶段之间是通过事件驱动的这种异步通讯模式来进行沟通及通信)
  • -
  • 真正的无连接数据报套socket支持(since 3.1)
  • -
-

性能

-
    -
  • 更高的吞吐量,更低的延迟
  • -
  • 减少资源消耗
  • -
  • 不必要的内存复制降到最低
  • -
-

安全

-
    -
  • 完整的SSL / TLS 和 StartTLS 支持
  • -
-

Netty 使用场景

-
    -
  1. 作为HTTP的服务器,类似与Jetty,Tomcat这种Servlet容器,只是Netty在充当HTTP的服务器时,它采用的编程模型并不是基于Servlet的规范,原因是Netty并没有实现Servlet的接口,Servlet的实现,Netty有自己的实现方式
  2. -
  3. 作为RPC通讯的框架,通讯的协议(可自定义),通讯的库,实现远程过程的调用,基于Socket方式(广泛使用)
  4. -
  5. 作为长连接的服务器,基于WebSocket,实现客户端和服务端之间的长连接通信
  6. -
-

环境说明

-
    -
  • System:macOS
  • -
  • Java:JDK1.8+
  • -
  • Netty:4.1.25.Final
  • -
-

HTTP

-

Netty 基于Servlet的规范,是一种特定的方式(更底层),因此 Netty 更专注于底层的性能等方面,所以在应用层开发时,是由开发人员自行去组装对请求,对 请求路由 处理等

-
-

示例:HTTP

-
-

Socket

-

Socket 是计算机网络中用于在节点内发送或接收数据的内部端点。具体来说,它是网络软件 (协议栈) 中这个端点的一种表示,包含通信协议、目标地址、状态等,是系统资源的一种形式

-
-

示例:Socket

-
-

Websocket

-

WebSocket 是HTML5 规范的一部分,也是基于 HTTP 协议之上的一种协议,WebSocket主要是解决 HTTP 上存在的一些问题;

-
    -
  1. HTTP 一种无状态(同一客户端发出的第一次请求接收到响应后,客户端发送第二次请求,这两次请求之间没有任何关联)的协议,HTTP 无法追踪某一请求来自哪一个客户端,客户端之前在服务器上存在一些信息(常见的解决方式:cookie,session)
  2. -
  3. HTTP 是基于请求响应模式的协议,请求的发起方一定是客户端,服务器将响应返回给客户端后连接就断掉了(HTTP 1.0),在 1.0 的基础上连接可以短时间的保持,一种 keep-alive 机制(HTTP 1.1)
  4. -
-

通常我们所使用的长连接技术

-
    -
  • 早期采用轮询的方式保持与服务器的连接
  • -
  • 目前通常采用 Websocket 的连接方式保持与服务器的连接 -
      -
    • 客户端(浏览器)与服务器建立连接后,没有其他因素干扰,连接是不会断,一直存在,客户端与服务器双方是对等的,不再区分谁是客户端,谁是服务端,客户端可以发送数据给服务端,服务端也可以发送数据给客户端,在真正意义上实现了服务端的推技术
    • -
    • 长连接在建立初期会发送带有 header 头信息的网络请求,在连接建立后,在长连接之上只需要发送需要传递的数据(真正的数据)即可
    • -
    • Websocket 是基于 HTTP 协议
    • -
    • Websocket 也可以用于非浏览器的场景,只要你的库支持 Websocket 即可
    • -
    -
  • -
-
-

示例:

-
    -
  • Websocket Server
  • -
  • Websocket Client
  • -
-
-

Heartbeat

-

对于服务器上的集群服务(zookeeper 或者其他的应用服务),或者是客户端与服务端之间的长连接,需要一种机制来检测客户端还是 alive,这种机制就是 heartbeat

-
    -
  • 对于服务器上的这些服务与服务之间,节点与节点之间的通信(无一例外都使用 TCP 连接通信),节点之间的通信如何保证(A 节点感知到 B 或者其他节点是未宕机),此时就需要心跳来检测对应的服务或节点还是正常的
  • -
  • 对于客户端与服务器之间由于网络问题,或者客户端开启飞行模式,或者关机等状态,服务端是无法感知,因此也需要借助心跳来检测客户端是否关机或开启了飞行模式
  • -
-
-

示例:Heartbeat

-
-

总结

-

Netty 程序编写步骤

-
    -
  1. 定义好父子的(bossGroup:获取链接,workerGroup:真正来处理链接)线程组(EventLoopGroup),服务器启动时关联一个处理器处理器类似Initializer这样的处理器
  2. -
  3. Initializer定义好自定义的或Netty本身提供的ChannelHandler通道处理器,在initChannel中自定义添加若干个处理器
  4. -
  5. 实现自定义处理器ChannelHandler中特定的回调方法
  6. -
-

Netty 程序测试

-
    -
  1. 启动 Server 服务
  2. -
  3. 访问启动的服务,使用 curl命令 或者使用浏览器访问进行访问
  4. -
-

netty-test

-]]>
- - Netty - - - Netty - -
- - Http VS Https - /2018/06/22/network-http/ - 基础名称 -

请求报文

-

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:
-请求行(request line)、请求头(header)、请求内容组成,如下请求报文的一般格式。
-请求报文

- -

请求行

-
    -
  1. -

    方法:

    -
      -
    • GET: 获取资源
    • -
    • POST: 向服务器端发送数据,传输实体主体
    • -
    • PUT: 传输文件
    • -
    • HEAD: 获取报文首部
    • -
    • DELETE: 删除文件
    • -
    • OPTIONS: 询问支持的方法
    • -
    • TRACE: 追踪路径
    • -
    -
  2. -
  3. -

    URL:
    -scheme://host:port/path?query

    -
      -
    • scheme: 表示协议,如Http, Https, Ftp等
    • -
    • host: 表示所访问资源所在的主机名:如:www.baidu.com
    • -
    • port: 表示端口号,Http默认为80,Https默认为443
    • -
    • path: 表示所访问的资源在目标主机上的储存路径
    • -
    • query: 表示查询条件
    • -
    -
  4. -
  5. -

    协议/版本号:

    -
  6. -
-

请求头

-
    -
  1. 通用首部(General Header)
  2. -
  3. 请求首部(Request Header)
  4. -
  5. 实体首部(Entity Header Fields)
  6. -
-

请求内容

-

如: 客户端POST的数据就放在这里(对比:GET的数据放在请求行的URL里)

-

例如:
-请求示例

-

响应报文

-

服务端响应一个HTTP请求消息包括以下格式:
-响应行(response line)、响应头(header)、响应内容组成

-

响应行

-
    -
  1. 状态码: -
      -
    • 1XX:Informational(信息性状态码)
    • -
    • 2XX:Success(成功状态码)
    • -
    • 3XX:Redirection(重定向)
    • -
    • 4XX:Client Error(客户端错误状态码)
    • -
    • 5XX:Server Error(服务器错误状态吗)
    • -
    -
  2. -
  3. 状态码描述:
  4. -
  5. 协议/版本号:
  6. -
-

响应头

-
    -
  1. 通用首部(General Header)
  2. -
  3. 响应首部(Response Header)
  4. -
  5. 实体首部(Entity Header Fields)
  6. -
-

响应内容

-

如:服务器返回的HTML、JSON等数据

-

响应示例

-

Http

-

概念

-
    -
  • HTTP:超文本传输协议(HyperText Transfer Protocol)是一种用于分布式、协作式和超媒体信息系统的应用层协议.
  • -
  • HTTP是万维网的数据通信的基础.
  • -
-

通信

-
    -
  1. 建立TCP连接
    -在HTTP工作开始之前,Client首先要通过网络与Service建立连接,该连接是通过TCP来完成的,HTTP是比TCP更高层次的应用层协议,根据规则,只有低层协议建立之后才能进行更高层协议的连接,因此,首先要建立TCP连接
  2. -
  3. Client发起HTTP请求(Request)
    -Requset通常包含请求行,请求头,请求内容这三部风组成的请求报文
  4. -
  5. Service发送HTTP响应(Response)
    -Response通常包含响应行,响应头,响应内容这三部风组成的响应报文
  6. -
  7. Client关闭TCP连接
  8. -
-

特点

-
    -
  1. 无状态 -
      -
    • 每个请求结束后都会被关闭,每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况
    • -
    • 服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器
    • -
    -
  2. -
  3. 明文传输,可能被窃听
  4. -
  5. 不验证通信方的身份,可能遭遇伪装 -
      -
    • HTTP 协议中的请求和响应不会对通信方进行确认。也就是说存在“服务器是否就是发送请求中 URI 真正指定的主机,返回的响应是否真的返回到实际提出请求的客户端”等类似问题
    • -
    • HTTP 协议通信时,由于不存在确认通信方的处理步骤,任何人都可以发起请求
    • -
    -
  6. -
  7. 无法证明报文的完整性,可能遭遇篡改 -
      -
    • 在请求或响应送出之后直到对方接收之前的这段时间内,即使请求或响应的内容遭到篡改,也没有办法获悉
    • -
    -
  8. -
-

Https

-

概念

-
    -
  • HTTPS:超文本传输安全协议(Hypertext Transfer Protocol Secure,常称为HTTP over TLS,HTTP over SSL或HTTP Secure)是一种通过计算机网络进行安全通信的传输协议.
  • -
  • HTTPS经由HTTP进行通信,但利用SSL/TLS来加密数据包.
  • -
-
-

HTTP+加密+认证+完整性保护 = HTTPS

-
-

HTTP VS HTTPS

-

通信

-

SSL/TLS

-

SSL/TLS:安全传输层协议(Transport Layer Security), 是介于TCP和HTTP之间的一层安全协议,TLS的前身是SSL(Secure Sockets Layer)

-
-

TLS/SSL关系

-
-
    -
  • SSL2.0
  • -
  • SSL3.0
  • -
  • TLS1.0(SSL3.1)
  • -
  • TLS1.1(SSL3.2)
  • -
  • TLS1.2(SSL3.3)
  • -
-

SSL/TLS工作原理

-

HTTPS协议的主要功能都依赖于SSL/TLS协议,SSL/TLS的功能实现主要依赖于三类算法:对称加密,非对称加密,散列函数Hash

-
    -
  • 非对称加密实现身份认证和密钥协商,
  • -
  • 对称加密算法采用协商的密钥对数据加密,
  • -
  • 基于散列函数验证信息的完整性
  • -
-

SSL/TLS协议实现

-

TLS以记录协议(record protocol)实现。记录协议负责在传输连接上交换所有的底层消息,并可以配置加密。每一条TLS记录以一个短标头起始。标头包含记录内容的类型(或子协议)、协议版本和长度

-

TLS的主规格说明书定义了四个核心子协议:

-
    -
  • 握手协议(handshake protocol);
  • -
  • 密钥规格变更协议(change cipher spec protocol);
  • -
  • 应用数据协议(application data protocol);
  • -
  • 警报协议(alert protocol);
  • -
-

握手协议

-

握手是TLS协议中最精密复杂的部分。在这个过程中,通信双方协商连接参数,并且完成身份验证。根据使用的功能的不同,整个过程通常需要交换6~10条消息。根据配置和支持的协议扩展的不同,交换过程可能有许多变种,在使用中经常可以观察到以下三种流程:

-
    -
  • 单向验证(完整的握手,对服务器进行身份验证)
  • -
  • 双向验证(对客户端和服务器都进行身份验证的握手)
  • -
  • 简短握手(恢复之前的会话)
  • -
-
单向验证
-

单向验证

-
    -
  1. Handshake:ClentHello
    -客户端通过发送 Client Hello 报文开始 SSL通信。报文中包含客户端支持的 SSL的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。
  2. -
  3. Handshake:ServerHello
    -服务器可进行 SSL通信时,会以 ServerHello 报文作为应答。和客户端一样,在报文中包含 SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
  4. -
  5. Handshake:Certificate
    -之后服务器发送 Certificate 报文。报文中包含公开密钥证书。
  6. -
  7. Handshake:ServerHelloDone
    -最后服务器发送 ServerHelloDone 报文通知客户端,最初阶段的 SSL握手协商部分结束。
  8. -
  9. Handshake:ClientKeyExchange
    -SSL第一次握手结束之后,客户端以 ClientKeyExchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-mastersecret 的随机密码串。该报文已用3 中的公开密钥进行加密。
  10. -
  11. ChangeCipherSpec
    -接着客户端继续发送 ChangeCipherSpec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
  12. -
  13. Handshake:Finished
    -客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
  14. -
  15. ChangeCipherSpec
    -服务器同样发送 ChangeCipherSpec 报文。
  16. -
  17. Handshake:Finished
    -服务器同样发送 Finished 报文。
  18. -
  19. Application Data(HTTP)
    -服务器和客户端的 Finished 报文交换完毕之后,SSL连接就算建立完成。当然,通信会受到 SSL的保护。从此处开始进行应用层协议的通信,即发送 HTTP 请求。
  20. -
  21. Application Data(HTTP)
    -应用层协议通信,即发送 HTTP 响应。
  22. -
  23. Alert:warning,close notify
    -最后由客户端断开连接。断开连接时,发送 close_notify 报文(上图做了一些省略,实际到这一步还需要发送TCP FIN报文关闭TCP链接)
  24. -
-
双向验证
-

双向验证
-同单向验证流程相比,双向验证多了如下两条消息:CertificateRequestCertificateVerify,其余流程大致相同

-
    -
  • CertificateRequest
    -CertificateRequest是TLS规定的一个可选功能,用于服务器认证客户端的身份。通过服务器要求客户端发送一个证书实现,服务器应该在ServerKeyExchange之后立即发送CertificateRequest消息
  • -
  • CertificateVerify
    -当需要做客户端认证时,客户端发送CertificateVerify消息,来证明自己确实拥有客户端证书的私钥。这条消息仅仅在客户端证书有签名能力的情况下发送
  • -
-

应用数据协议(application data protocol)

-

应用数据协议携带着应用消息,只以TLS的角度考虑的话,这些就是数据缓冲区。记录层使用当前连接安全参数对这些消息进行打包、碎片整理和加密

-

警报协议(alert protocol)

-

警报的目的是以简单的通知机制告知对端通信出现异常状况。它通常会携带close_notify异常,在连接关闭时使用,报告错误

-

附录

-
    -
  • 《图解HTTP》
  • -
  • HTTP | MDN
  • -
  • 数字证书及CA的扫盲介绍
  • -
  • HTTPS 原理浅析及其在 Android 中的使用
  • -
-]]>
- - Network - - - Http - Https - -
- - Network(一) 之OkHttp 入门 - /2018/06/23/network-okhttp1/ - 自从Android4.4的源码中可以看到HttpURLConnection已经替换成OkHttp开始( JakeWharton曾在Twitter表示 ) ,OkHttp+Retrofit+RxJava的组合网络请求一直经久不衰,主流app的网络架构基本都是这样的组合模式,存在即合理,说明OkHttp+Retrofit+RxJava的方式确实给开发,用户体验等带来可观的优势,那么这个系列文章围绕Android的网络展开.

-

OkHttp:An HTTP & HTTP/2 client for Android and Java applications

- -
-

Android 历史网络库

-
-
    -
  • HttpClient 是 Apache 提供的HTTP网络访问接口,从一开始的时候就被引入到了Android的API中;
  • -
  • HttpURLConnection 是一种多用途, 轻量极的HTTP客户端, 提供的API比较简单, 可以容易地去使用和扩展.
  • -
-

OkHttp优势

-
    -
  • 支持HTTP/2, HTTP/2通过使用多路复用技术在一个单独的TCP连接上支持并发, 通过在一个连接上一次性发送多个请求来发送或接收数据
  • -
  • 如果HTTP/2不可用, 连接池复用技术也可以极大减少延时
  • -
  • 支持GZIP, 可以压缩下载体积
  • -
  • 响应缓存可以直接避免重复请求
  • -
  • 会从很多常用的连接问题中自动恢复
  • -
  • 如果您的服务器配置了多个IP地址, 当第一个IP连接失败的时候, OkHttp会自动尝试下一个IP
  • -
  • OkHttp还处理了代理服务器问题和SSL握手失败问题,等等…
  • -
-

基本使用

-

该系列版本说明

-
    -
  • OkHttp版本统一:3.10.0
  • -
  • JDK:1.8+
  • -
-

Gradle包导入

-
// okhttp核心库
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
// okhttp网络请求拦截日志库
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
-
-

关于网络请求
-基本网络请求由请求(请求行请求头请求内容),响应(响应行响应头响应内容)两大部分组成,具体的内容请查看Http VS Https这篇文章

-
-

OkHttp请求

-

已在Http VS Https文章中介绍了,HTTP请求相关内容

-

OkHttp响应

-

已在Http VS Https文章中介绍了,HTTP响应相关内容

-

同步与异步

-

网络请求执行方式为:同步与异步;同步异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)

-

同步

-

就是在发出一个 调用 时,在没有得到结果之前,该 调用 就不返回,但是一旦调用返回,就得到返回值了。
-换句话说,就是由 调用者 主动等待这个 调用 的结果。
-Okhttp同步(execute()):Invokes the request immediately, and blocks until the response can be processed or is in error.

-
String url = "https://api.github.com/users/BladeCode";
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
// 执行同步操作
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
-

异步

-

异步 则与同步相反,调用 在发出之后,这个调用就直接返回了,所以没有返回结果。
-换句话说,当一个异步过程调用发出后,调用者 不会立刻得到结果。而是在 调用 发出后,被调用者 通过状态、通知来通知 调用者,或通过回调函数处理这个调用。
-Okhttp同步(enqueue(Callback responseCallback)):Schedules the request to be executed at some point in the future.

-
String url = "https://api.github.com/users/BladeCode";
OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder().url(url).build();
// 返回response 对象
Response response = client.newCall(request).enqueue(new Callback() {

@Override
public void onFailure(Call call, IOException e) {
System.out.println(e.toString());
}

@Override
public void onResponse(Call call, Response response) throws IOException {
// 字符串形式表达响应
System.out.println(response.body().string());
// 或流的形式表达响应
System.out.println(response.body().charStream());
System.out.println(response.body().byteStream());
}
});
-
-

注意:

-
-
    -
  • 响应体太大(超过1MB), 应避免使用 string()方法, 因为它会将把整个文档加载到内存中.
  • -
  • 对于超过1MB的响应body, 应使用流的方式来处理响应body. 这和我们处理xml文档的逻辑是一致的, 小文件可以载入内存树状解析, 大文件就必须流式解析
  • -
-

OkHttp Get

-
String url = "https://api.github.com/users/BladeCode";
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();

if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
-

OkHttp Post

-
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();

Response response = client.newCall(request).execute();

if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}

}
-

Posting a String

-
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
String postBody = ""
+ "Releases\n"
+ "--------\n"
+ "\n"
+ " * _1.0_ May 6, 2013\n"
+ " * _1.1_ June 15, 2013\n"
+ " * _1.2_ August 11, 2013\n";

Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-
-

注意:当提交数据大于1MB,请使用流的方式

-
-

Post Streaming

-
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
RequestBody requestBody = new RequestBody() {
@Override
public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}

private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + " × " + i;
}
return Integer.toString(n);
}
};

Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-

Posting a File

-
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
File file = new File("README.md");

Request request = new Request.Builder()
.url("https://api.github.com/BladeCode/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-

Posting form parameters

-

使用FormEncodingBuilder来构建和HTML标签相同效果的请求体。键值对将使用一种HTML兼容形式的URL编码来进行编码

-
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
RequestBody formBody = new FormEncodingBuilder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-

Posting a multipart request

-

MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-LengthContent-Type可用的话,他们会被自动添加到请求头中。

-
private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();

Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();

try(Response response = client.newCall(request).execute()){
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
- -

通常,HTTP headers 的工作方式类似于 Map<String, String>:每个字段都有一个值或没有,但是一些headers允许多个值

-
    -
  • 例如:Guava’s Multimap.
  • -
  • 例如:提供多个vary headers的HTTP响应是合法且常见的。OkHttp的API试图使用两种情况都很舒适
  • -
-

在编写请求headers时

-
    -
  • 使用 header(name, value)name 的唯一内容设置为 value。如果 name 存在现有值,则在添加新值之前将删除它。
  • -
  • 使用 addHeader(name, value) 添加 headers 不会删除已存在的 header
  • -
-

在读取headers响应时,使用 header(name) 返回最后异常出现的命名值。通常这也是唯一发生,如果没有值,则 header(name) 返回null。将所有字段的值作为列表读取,请使用 headers(name)

-
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println("Server: " + response.header("Server"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Vary: " + response.headers("Vary"));
}
}
-

Response Caching

-

实现缓存响应,你需要一个可以读写的缓存目录,以及缓存大小的限制。缓存目录应该是私有的,不受信任的应用程序不应该读取其内容

-

让多个缓存同时访问同一缓存目录是错误的。大多数应用程序应该只调用一次 new OkHttpClient(),使用它们的缓存配置它,并在任何地方使用相同的实例。否则,两个缓存实例将互相踩踏,破坏响应缓存,并可能导致程序奔溃

-

响应缓存使用HTTP headers进行所有的配置。你可以添加headers,如:Cache-Control: max-stale=3600,OkHttp的缓存将遵循它。你的Web服务器使用自己的响应headers配置缓存响应的时间,例如:Cache-Control: max-age=9600。有缓存headers可强制缓存响应,强制网络响应,或者强制使用条件GET验证网络响应

-
private final OkHttpClient client;

public CacheResponse(File cacheDirectory) throws Exception {
// 设置缓存大小 10 MiB
int cacheSize = 10 * 1024 * 1024;
// 实例化Cache对象
Cache cache = new Cache(cacheDirectory, cacheSize);

client = new OkHttpClient.Builder()
.cache(cache)
.build();
}

public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();

String response1Body;
try (Response response1 = client.newCall(request).execute()) {
if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);

response1Body = response1.body().string();
System.out.println("Response 1 response: " + response1);
System.out.println("Response 1 cache response: " + response1.cacheResponse());
System.out.println("Response 1 network response: " + response1.networkResponse());
}

String response2Body;
try (Response response2 = client.newCall(request).execute()) {
if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);

response2Body = response2.body().string();
System.out.println("Response 2 response: " + response2);
System.out.println("Response 2 cache response: " + response2.cacheResponse());
System.out.println("Response 2 network response: " + response2.networkResponse());
}

System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
}
-
    -
  • 要阻止响应使用缓存,请使用 CacheControl.FORCE_NETWORK
  • -
  • 要阻止它使用网络,请使用 CacheControl.FORCE_CACHE
  • -
-
-

警告:如果你使用 FORCE_CACHE 且响应需要网络,OkHttp将返回504不满意请求响应

-
-

Canceling a Call

-

使用 Call.cancel() 立即停止正在进行的请求,如果线程当前正在请求或读取响应,则它将收到 IOException。当不在需要call时,使用它来保护网络,例如,当你的用户导航离开应用程序时,同步和异步调用都可以取消

-
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
.build();

final long startNanos = System.nanoTime();
final Call call = client.newCall(request);

// Schedule a job to cancel the call in 1 second.
executor.schedule(new Runnable() {
@Override public void run() {
System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
call.cancel();
System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
}
}, 1, TimeUnit.SECONDS);

System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
try (Response response = call.execute()) {
System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
(System.nanoTime() - startNanos) / 1e9f, response);
} catch (IOException e) {
System.out.printf("%.2f Call failed as expected: %s%n",
(System.nanoTime() - startNanos) / 1e9f, e);
}
}
-

Timeouts

-

当无法访问时,使用超时来使call失败。网络分区可能是由于客户端连接问题,服务器可读性问题或其他任何问题时。OkHttp支持连接,读取和写入超时配置

-
private final OkHttpClient client;

public ConfigureTimeouts() throws Exception {
client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}

public void run() throws Exception {
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
.build();

try (Response response = client.newCall(request).execute()) {
System.out.println("Response completed: " + response);
}
}
-

Per-call Configuration

-

所有的HTTP 客户端配置都在 OkHttpClient 中,包括代理设置,超时和缓存。当你需要修改单个调用时的配置时,请调用 OkHttpClient.newBuilder()。这将返回与原始客户端共享相同连接池,调度程序和配置的构建器(Builder

-
// 示例:我们发出一个请求,其中500毫秒超时,另一个请求超时3000毫秒
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
Request request = new Request.Builder()
// This URL is served with a 1 second delay.
.url("http://httpbin.org/delay/1")
.build();

// Copy to customize OkHttp for this request.
OkHttpClient client1 = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
try (Response response = client1.newCall(request).execute()) {
System.out.println("Response 1 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 1 failed: " + e);
}

// Copy to customize OkHttp for this request.
OkHttpClient client2 = client.newBuilder()
.readTimeout(3000, TimeUnit.MILLISECONDS)
.build();
try (Response response = client2.newCall(request).execute()) {
System.out.println("Response 2 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 2 failed: " + e);
}
}
-

Handling authentication

-

OkHttp可以自动重试未经身份验证的请求。如果响应为401 Not Authorized,则要求Authenticator提供凭证。实现应该构建一个包含缺少凭证的新请求。如果没有可用的凭证,则返回null以跳过重试。

-

使用 Response.challenges()来获取任何身份验证挑战的方案和领域。在完成基本挑战时,使用 Credentials.basic(username, password) 对请求header进行编码

-
private final OkHttpClient client;

public Authenticate() {
client = new OkHttpClient.Builder()
.authenticator(new Authenticator() {
@Override public Request authenticate(Route route, Response response) throws IOException {
if (response.request().header("Authorization") != null) {
return null; // Give up, we've already attempted to authenticate.
}

System.out.println("Authenticating for response: " + response);
System.out.println("Challenges: " + response.challenges());
String credential = Credentials.basic("jesse", "password1");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
})
.build();
}

public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/secrets/hellosecret.txt")
.build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}
}
-

为避免在身份验证不起作用时进行多次重试,你可以在返回null以放弃,例如,你可能希望在尝试这些确切凭证时跳过重试

-
if (credential.equals(response.request().header("Authorization"))) {
return null; // If we already failed with these credentials, don't retry.
}
-

当你达到应用程序定义的尝试限制时,你也可以跳过重试

-
private int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}

if (responseCount(response) >= 3) {
// If we've failed 3 times, give up.
return null;
}
-

附录

-
    -
  • OkHttp Wiki
  • -
  • 怎样理解阻塞非阻塞与同步异步的区别
  • -
  • OkHttp使用教程
  • -
-]]>
- - Network - - - OkHttp - -
- - OOAD 与 UML - /2019/05/27/ooad-uml/ - OOAD(Object Oriented Analysis and Desigin) 是根据 OO 的方法学,对软件系统进行分析和设计的过程

-
    -
  • OOA(Object Oriented Analysis):分析阶段
  • -
  • OOD(Object Oriented Desigin):设计阶段
  • -
-

What to do

-

分析阶段主要解决以下问题

-
    -
  • 建立针对业务问题域的清晰视图
  • -
  • 列出系统必须要完成的核心任务
  • -
  • 针对问题域建立公共词汇表
  • -
  • 列出针对此问题域的最佳解决方案
  • -
- -

How to do

-

设计阶段主要解决以下问题(How to do?)

-
    -
  • 如何解决具体的业务问题
  • -
  • 引入系统工作所需的支持元素
  • -
  • 定义系统的实现策略
  • -
-

OOP的主要特征

-

抽象(abstract)

-
    -
  • 忽略到一个对象或实体的细节而只关注其本质特征的过程
  • -
  • 简化功能与格式
  • -
  • 帮助用户与对象交互
  • -
-

封装(encapsulation)

-
    -
  • 隐藏数据和实现
  • -
  • 提供公共方法供用户调用功能
  • -
  • 对象的两种视图 -
      -
    • 外部视图:对象能做的工作
    • -
    • 内部视图:对象如何完成工作
    • -
    -
  • -
-

继承(inheritance)

-
    -
  • 通过存在的类型定义新类型的机制
  • -
  • 通常在两个类型之间存在“is a” 或 “kind of” 这样的关系
  • -
  • 通过继承可实现代码重用,另外继承也是多态的基础
  • -
  • 如苹果 “is a” 水果
  • -
-

多态(polymorphism)

-
    -
  • 一个名称,多种形式
  • -
  • 基于继承的多态
  • -
  • 调用方法时根据所给对象的不同选择不同的处理方式
  • -
  • 如 Football——play()
  • -
  • 给出一个具体的足球或篮球,用户自动知道该使用谁的方式去执行
  • -
-

关联(association)

-
    -
  • 对象之间交互时的一种引用方式
  • -
  • 当一个对象通过对另一个对象的引用去使用另一个对象的服务或操作时,两个对象之间便产生了关联
  • -
  • 如 person 使用 computer,person 与 computer 之间就存在了关联关系
  • -
-

聚合(aggregation)

-
    -
  • 关联关系的一种,一个对象成为另一个对象的组成部分
  • -
  • 使用关系强的关联
  • -
  • 在两个对象之间存在 “has a”这样的关系,一个对象作为另一个对象的属性存在,在外部对象被产生时,可由客户端指定与其关联的内部对象
  • -
-
-

如汽车与轮胎,轮胎作为汽车的一个组成部分,它和汽车可由分别产生以后转配起来使用,但汽车可由换新轮胎,轮胎也可以卸下来给其他汽车使用

-
-

组合(composition)

-
    -
  • 当一个对象包含另一个对象时,外部对象负责管理内部对象的生命周期的情况
  • -
  • 关联关系中最为强烈的一种
  • -
  • 内部对象的创建由外部对象自己控制
  • -
  • 外部对象不存在时,内部对象也不能存在
  • -
-
-

如电视机与显示器

-
-

内聚与耦合(cohesion & coupling)

-
    -
  • 域模型是面向对象的。在面向对象术语中域模型也可称为设计模型。
  • -
  • 域模型由以下内容组成 -
      -
    • 关联(Association):一对多,多对一,一对一
    • -
    • 依赖(Dependency)
    • -
    • 聚集(Aggregation):整体和部分之间的关系
    • -
    • 一般化(泛化)(Generalization):类与类之间的继承
    • -
    -
  • -
  • 内聚:度量一个类独立完成某项工作的能力
  • -
  • 耦合:度量系统内或系统之间依赖关系的复杂度
  • -
  • 设计原则:增加内聚,减少耦合
  • -
-

开发过程概述

-

传统开发过程

-
    -
  • 瀑布模型(真实环境,不可能满足这些)
  • -
-

统一软件开发过程(USDP)

-

特点: 项目是迭代,递增

-
    -
  • 迭代指生命周期中的一个步骤
  • -
  • 迭代导致“递增”或者是整个项目的增长
  • -
  • 大项目分解为子项目
  • -
  • 在每一个迭代的阶段,应该做以下工作 -
      -
    • 选择并分析相关用例
    • -
    • 更加所选架构进行设计
    • -
    • 在组件层次实现设计
    • -
    • 验证组件满足用例的需要
    • -
    -
  • -
  • 当一次迭代满足目标后,开发进入下一个迭代周期
  • -
  • 每一个周期包含一次或多次迭代
  • -
  • 一个阶段的结束称之为“里程碑”
  • -
-

阶段

-

初始化阶段

-

该阶段的增量集中于:

-
    -
  • 项目启动
  • -
  • 建立业务模型
  • -
  • 定义业务问题域
  • -
  • 找出主要的风险因素
  • -
  • 定义项目需求的外延
  • -
  • 创建业务问题域的相关说明文档
  • -
-

细化阶段

-

本阶段的增量集中于

-
    -
  • 高层的分析与设计
  • -
  • 建立项目的基础框架
  • -
  • 监督主要的风险因素
  • -
  • 制订达成项目目标的创建计划
  • -
-

构建阶段

-

本阶段的增量集中于

-
    -
  • 代码及功能的实现
  • -
-

移交阶段

-

本阶段的增量集中于:

-
    -
  • 向用户发布产品
  • -
  • beta 测试
  • -
  • 执行性能调优,用户培训和接收测试
  • -
-

阶段特点

-

每一个阶段所包含的工作流,每一次递增都由 5 个部分工作流组成

-
    -
  • 需求与初始化分析
  • -
  • 分析
  • -
  • 设计
  • -
  • 实现
  • -
  • 测试
  • -
  • 每一次迭代执行工作流的深度不同
  • -
  • 早期的迭代在深度上覆盖初始工作流,后期迭代在深度上覆盖后期工作流
  • -
  • 80/20原则
  • -
-

UML

-

UML(Unified Modeling Language)统一建模语言,图形化语言表示,它可以帮助我们在 OOAD 过程中标识元素,构建模块,分析过程并可通过文档说明系统中的重要细节

-

静态模型(static model)

-
    -
  • 创建并记录一个系统的静态特征
  • -
  • 反映一个软件系统基础,固定的框架结构
  • -
  • 创建相关问题域主要元素的视图
  • -
  • 静态建模包括: -
      -
    • 用例图(use case diagrams)
    • -
    • 类图(class diagrams)
    • -
    • 对象图(object diagrams)
    • -
    • 组件图(component diagrams)
    • -
    • 部署图(deployment diagrams)
    • -
    -
  • -
-

动态模型(dynamic model)

-
    -
  • 用以展示系统的行为
  • -
  • 动态建模包括: -
      -
    • 时序图(sequence diagrams)
    • -
    • 协作图(collaboration diagrams)
    • -
    • 状态图(state chart diagrams)
    • -
    • 活动图(activity diagrams)
    • -
    -
  • -
-

UML 其他重要元素

-
    -
  • 包(package)
  • -
  • UML 的扩展机制 -
      -
    • 注释(comments)
    • -
    • 构造型(stereotypes)
    • -
    • 标记值(tagged values)
    • -
    • 限制(constraints)
    • -
    -
  • -
-

示例

-

用例图

-
    -
  • 展示系统的核心功能及逾期交互的用户
  • -
  • 用户被称为“活动者”(Actor)
  • -
  • 用例使用椭圆表示
  • -
  • 为简化建模过程,用例图可以标注优先级
    -uml-usecase-diagram
  • -
-

类图

-
    -
  • 表现类的特征
  • -
  • 类图描述了多个类,接口的特征,以及对象之间的协作与交互
  • -
  • 由一个或者多个矩形区域构成,内容包括 -
      -
    • 类型(类名)
    • -
    • 属性(可选)
    • -
    • 操作(可选)
      -uml-class-diagram
    • -
    -
  • -
-

对象图

-
    -
  • 表现对象的特征
  • -
  • 对象图展现了多个对象的特征及对象之间的交互
    -uml-object-diagram
  • -
-

组件图

-
    -
  • 表示软件组件之间的关系
    -uml-component-diagram
  • -
-

部署图

-
    -
  • 表现用于部署软件应用的物理设备信息
    -uml-deployment-diagram
  • -
-

时序图

-
    -
  • 捕捉一段时间范围内多个对象之间的交互信息
  • -
  • 强调消息交互的时间顺序
  • -
-]]>
- - DevTool - - - OOAD - UML - -
- - 开源协议,该如何选择 - /2020/11/25/open-license/ - 现如今软件行业的发展完全离不开开源社区,很多优秀的软件应用、技术都能看到开源软件的影子,我们都是站在巨人的肩膀上。对于软件行业的从业者,能为开源项目贡献自己的力量,或是将自己对某一个细分领域所做的研究实践开源出来,这是一件非常值得骄傲的事情。而要参与一个大型的开源项目,你除了需要该项目涉及的核心技术知识外,还需要了解一定的开源项目运转方式等,对于如何参与开源项目,这里暂不做过多的介绍,有兴趣的可以移步 Gitee 发起的《开源指北》项目,该项目中详细介绍了如何参与开源项目。本篇文章也不啰嗦这一点,仅仅围绕开源协议,我们应该清楚的常识和注意的点

- -

在软件开发中通常有两种情况我们需要考虑软件的开源协议或者使用协议

-
    -
  1. 我们需要使用到业界的一些优秀的软件包来提高我们开发的效率,避免了重复早轮子,所选择的这些软件包我们不但考虑功能的同时,也要考虑软件包的授权协议
  2. -
  3. 我们需要将自己的经验或者软件产品需要开源时,为了保护自己的权益,我们也需要选择一个合适的开源协议
  4. -
-

我这里还是引用比较经典 阮一峰 文章中所绘制关于如何选择开源协议的图
-
-图中已经很清楚的表示了如何去选择 LGPL, Mozilla, GPL, BSD, MIT, Apache 这 6 种协议

-

常用协议

-

这里我们通过表格的形式介绍下这 6 种协议,当然除了表中列出的这些协议之外还有很多协议,我们就挨个来简单对他们有一个了解和认识

-

-

其他协议

-

BY-NC-SA

-

你会发现每篇文章下面都有申明版权,这里使用的是 BY-NC-SA 4.0 的协议,他们的含义如下

-
    -
  • :知识共享(CreativeCommons)
  • -
  • NC:非商业性使用(NonCommercial),您不得将本作品用于商业目的
  • -
  • SA:相同方式共享(ShareAlike),如果您再混合、转换或者基于本作品进行创作,您必须基于与原先许可协议相同的许可协议 分发您贡献的作品
  • -
-

使用此协议,您可以自由地

-
    -
  1. 共享 — 在任何媒介以任何形式复制、发行本作品
  2. -
  3. 演绎 — 修改、转换或以本作品为基础进行创作
  4. -
-
-

只要你遵守许可协议条款,许可人就无法收回你的这些权利

-
-

选择

-

上面说了那么多,有些协议并没有展开来说可能并不适用你当前的所需要选择的协议,那么你可以根据实际情况去筛选,可通过 https://choosealicense.com, https://kaiyuanshe.cn/license-tool 这两个网站按照步骤去选择,最终确定协议即可

-

参考

-
    -
  1. 开源许可证选择器
  2. -
  3. Choose an open source license
  4. -
  5. 博云违反 Apache 2.0 开源协议被要求整改,开源协议到底应该如何遵守?
  6. -
  7. 开源协议是什么?有哪些?如何选择?
  8. -
  9. 如何为你的代码选择一个开源协议
  10. -
  11. 开源指北 Gitee
  12. -
-]]>
- - Open Source - - - License - -
- - OSS 初体验 - /2021/03/27/oss/ - 在之前 SpringBoot(十二)文件上传 文章中,已经学习了使用 SpringBoot 基础的功能,完成静态资源的管理,本片文章我们同样也是对非结构化的静态数据进行管理,不过这次我们使用的是比较常用的 OSS 服务,废话不说,我们一起开始 OSS 之旅吧

- -

什么是 OSS

-

OSS 是一种面向海量数据规模的分布式存储服务,具有稳定,可靠,安全,低成本的特点。主要用来存储各种非结构化的数据,比如视频,图像,日志,文本文件等。OSS 服务提供标准的 RESTful API 接口,并提供一些常用语言的 SDK 包,方便开发者进行快速开发和二次处理

-

常用的 OSS

-

市面上提供云服务的厂商有很多,这里以阿里云的 OSS 服务为主来,完成 OSS 相关的学习和实践

-

依赖

-
<!-- https://mvnrepository.com/artifact/com.aliyun.oss/aliyun-sdk-oss -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.11.1</version>
</dependency>
-

OSS 工具类

-
/**
* OSS 文件上传
*
* @author : Jerry xu
* @since : 2020/11/3 09:12
*/
@Slf4j
public class OssUtils {

// 访问域名
private static final String ENDPOINT = "xxxxx";
// 存储空间
private static final String BUCKET_NAME = "xxxxx";
//==================访问密钥==================
private static final String ACCESS_KEY_ID = "xxxxx";
// 用户用于加密签名字符串和OSS用来验证签名字符串的密钥,必须保密
private static final String ACCESS_KEY_SECRET = "xxxxx";

/**
* 通过文件上传图片
*
* @param file 文件
* @return 上传结果地址
*/
public static String uploadFileByFile(File file) {
// // NIO 方式
// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());
// // 其他方式
// byte[] fileByte = Files.readAllBytes(Paths.get(file.getPath()));
// return uploadFileByByte(fileByte, file);
URL url;
String urlStr = null;
String[] split = new String[0];
try {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
// 获取文件名
String fileName = file.getName();
ossClient.putObject(BUCKET_NAME, fileName, file);
// 设置过期时间
url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, new Date(System.currentTimeMillis() + 3600 * 24 * 365 * 10));
log.info("原始图片地址:{}", url);
urlStr = url.toString();
// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());
// PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, "test", new ByteArrayInputStream(fileByte));
// // 不设置过期时间
// PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);
// // 去除过期时间参数地址
// split = urlStr.split("\\?");
} catch (Exception e) {
log.warn(e.getMessage());
}
return urlStr;
}

/**
* 通过字节数组上传图片
*
* @param binaryBytes 字节数组
* @param fileName 文件名
* @return 上传结果地址
*/
public static String uploadFileByByte(byte[] binaryBytes, String fileName) {
InputStream inputStream = new ByteArrayInputStream(binaryBytes);
return uploadFileByInputStream(inputStream, fileName);
}

/**
* 通过输入流上传图片
*
* @param inputStream 输入流
* @param fileName 文件名
* @return 上传结果地址
*/
public static String uploadFileByInputStream(InputStream inputStream, String fileName) {
URL url;
String urlStr = null;
String[] split = new String[0];
try {
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
// String fileName = UUID.randomUUID().toString() + ".jpeg";
ossClient.putObject(BUCKET_NAME, fileName, inputStream);
// 设置过期时间
url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, new Date(System.currentTimeMillis() + 3600 * 24 * 365 * 10));
log.info("原始图片地址:{}", url);
urlStr = url.toString();
// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());
// PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, "test", new ByteArrayInputStream(fileByte));
// // 不设置过期时间
// PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);
// // 去除过期时间参数地址
// split = urlStr.split("\\?");
} catch (Exception e) {
log.warn(e.getMessage());
}
return urlStr;
}

}
-

上传

-

这里我们写一个上传接口

-
@ApiOperation("文件上传", notes = "支持多图上传")
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public List<String> uploadTest(@RequestParam("file") List<MultipartFile> file) {
List<String> uploadList = new ArrayList<>(file.size());
file.forEach(t -> {
try {
String url = OssUtils.uploadFileByInputStream(t.getInputStream(), t.getOriginalFilename());
uploadList.add(url);
} catch (IOException e) {
e.printStackTrace();
}
});
return uploadList;
}
-

测试

-

不废话了,直接看图就好了
-

-

问题

-
    -
  1. 对于上传获取到的文件地址是一个会过期的地址,并不是一个固定不变的地址,如上截图所示,我偷懒直接将地址链接出的相关参数删去,拿到了一个永久存储的访问连接地址。但这里需要注意,这需要在你的 OSS 管理后台去设置你的文件存储的过期策略。这里就不进行截图演示了(主要是我没有登录系统的账号密码,逃 ~)
  2. -
  3. 对于上传的文件我没有自定义文件名,这里有个问题是当用户上传 OSS 服务中已经存在的文件名的文件时,新上传的会覆盖旧文件,因此这个地方需要根据实际的业务场景选择合适的方式。在 OssUtils 工具类中我已经注释掉了将文件名重命名的代码,你可以在此处按照你的业务进行更改
  4. -
  5. 第三个问题就是结合上面的两点的汇总方案,其实呢,对于一般的系统,这些静态资源就存永久的连接地址即可。但目前新的系统对用户的资料等也有了 “稍微” 高一点的保护,就是这些资源都是有时效性的,获取的地址就是我们上传拿到的原始地址,而我们存放在数据库中当然也不会是之前那种永久的连接地址,而是对应图片的一个唯一标识信息(可以是重命名后的文件名或者其他能够唯一标识资源你的字段),然后用户访问这些资源时,用存放在数据库中的唯一标识去 OSS 服务上查询对应的资源,然后加载这个地址去显示。
  6. -
-

参考

-
    -
  1. 阿里云对象存储 OSS
  2. -
  3. 对象存储 Kodo
  4. -
  5. 华为云对象存储服务 OBS
  6. -
-]]>
- - OSS - - - OSS - -
- - 迷宫如意琳琅图籍 - /2020/12/10/play-maze/ -

- -

故宫博物院出品,奥秘之家设计制作(曾推出线下实景地铁逃脱游戏,2018联合《唐人街探案 2》推出《侦探笔记》的互动解密游戏,以及配合电影开发Crimaster),到手快一年了还没有完全解锁线上的关卡,倒不是玩不下去,而是懒,刷 B 站多香,动啥脑子,哈哈哈。言归正传,本篇记录自己解锁线上关卡的步骤,持续更新

-

进度

-
    -
  1. 初 ------------------ 100% -
      -
    • ✅ 梦入紫禁
    • -
    • ✅ 太和异象
    • -
    • ✅ 十八棵槐
    • -
    -
  2. -
  3. 壹 ------------------ 23% -
      -
    • 万寿盛筵
    • -
    • 殿前观礼
    • -
    • 礼乐度量
    • -
    • 一等画师
    • -
    • 腰牌买卖
    • -
    • 慈宁画样
    • -
    -
  4. -
  5. 贰 ------------------ 0% -
      -
    • 宫女禾心
    • -
    • 嘉祉初遇
    • -
    • 淑芳听戏
    • -
    • 戏里玄机
    • -
    • 上元之约
    • -
    -
  6. -
  7. 叁 ------------------ 0% -
      -
    • 结伴寻宝
    • -
    • 皇十五子
    • -
    • 档房探秘
    • -
    • 一路狂奔
    • -
    • 逢凶化吉
    • -
    • 五行八卦
    • -
    • 夜探御园
    • -
    -
  8. -
  9. 肆 ------------------ 0% -
      -
    • 琳琅宝藏
    • -
    • 花叶之谜
    • -
    • 宫中怪人
    • -
    • 图籍作者
    • -
    • 祸不单行
    • -
    • 五蕴皆空
    • -
    -
  10. -
  11. 伍 ------------------ 0% -
      -
    • 将破未破
    • -
    • 孤注一掷
    • -
    -
  12. -
  13. 隐 ------------------ 0% -
      -
    • 多年以后
    • -
    -
  14. -
  15. 众 ------------------ 0% -
      -
    • 众筹专属
    • -
    -
  16. -
-

-

梦入紫禁

-

太和异象

-

十八棵槐

-

-

万寿盛筵

-

殿前观礼

-

礼乐度量

-

一等画师

-

腰牌买卖

-

慈宁画样

-

-

宫女禾心

-

嘉祉初遇

-

淑芳听戏

-

戏里玄机

-

上元之约

-

-

结伴寻宝

-

皇十五子

-

档房探秘

-

一路狂奔

-

逢凶化吉

-

五行八卦

-

夜探御园

-

-

琳琅宝藏

-

花叶之谜

-

宫中怪人

-

图籍作者

-

祸不单行

-

五蕴皆空

-

-

将破未破

-

孤注一掷

-

-

多年以后

-

-

众筹专属

-

附录

-
    -
  1. 奥秘之家官网:http://www.itaotuo.com/
  2. -
  3. 《唐人街探案 2》之《侦探笔记》:https://www.zhihu.com/question/267341464
  4. -
  5. 《唐人街探案 3》之《侦探笔记》:https://zhongchou.modian.com/item/90315.html
  6. -
-]]>
- - Play - - - 迷宫 - -
- - 搞定 m.2 接口 SSD - /2020/12/10/play-ssd/ -

- -

公司原装配置电脑磁盘性能太差,实在是不能满足我的日常骚操作,然后就自己买了一个 m.2 接口的 SSD 硬盘,毕竟电脑之前已经有系统了,而且也已经安装好了开发环境,如果现在在新的 SSD 上直接安装新的系统,那么需要将之前的开发环境再折腾一遍,实在是伤不起。那么有没有别的方式。你别说哦,还真的有,方法是用一些工具对现有系统进行 clone 到新的 SSD 磁盘上。这都很好办,比如:傲梅分区助手DiskGenius 都有系统迁移功能,可参考文章下方的参考地址,内有视频教程

-
-

注意:要设置好设置默认系统启动引导为新的磁盘

-
-

问题

-

一开始,我觉得这么简单的操作能有什么问题,迁移完系统,并设置好系统引导,然而我发现并不能按照预期使用 SSD 来启动,试了好几遍,调整了 BIOS 的启动选项,依旧不能解决。后来我将原系统的磁盘拆下来,只留 SSD 磁盘,开机就能按照预期启动了,正常后在把原系统磁盘再装回去,同时记得检查下系统引导,确保还依旧是使用 SSD 系统盘

-

参考

-
    -
  1. SSD系统迁移工具:轻松迁移系统到SSD
  2. -
  3. 使用分区助手快速将Windows系统迁移到新磁盘
  4. -
-]]>
- - Play - - - SSD - -
- - Api 文档管理系统 RAP1环境搭建 - /2018/03/27/rap1/ - 前后端分离的路上,一款强大的API管理工具,可以降低沟通成本,大大提高开发效率,节省的时间,让我们去做更有意义的事情。

-

API管理工具有很多,选择适合自身需求的就是最好

-

这里以阿里妈妈出品的RAP产品;目前RAP分为: RAP1RAP2

- -
-

虽然RAP1不再添加新功能,只做维护工作,介于RAP2目前还不是很成熟,本篇文章先讲RAP1的搭建过程(虽然官方Wiki已经有很详细的部署教程,但在部署过程中还是遇到一些问题,因此就记录下来)

-
-

如果你不需要搭建,可以直接访问RAP1提供的服务http://rapapi.org

-

项目构建

-
    -
  • 系统环境:Windows 10 x64
  • -
  • 应用工具:GitIDEAJDK1.8+Tomcat8+MySQLRedis3+
  • -
-

这里Git,IDEA,JDK1.8,Tomcat8,MySQL不再赘述安装步骤以及环境配置

-

安装基本工具

-

Redis

-

由于Redis 官方并未支持Windows系统,因此借助MicrosoftArchive团队所提供的Windows Redis安装包,这里下载最新的Redis-x64-3.2.100.msi

-
    -
  • -

    以管理员身份运行安装包Redis-x64-3.2.100.msi

    -
      -
    1. 添加环境变量
      -env
    2. -
    3. 默认6379端口
      -port
    4. -
    5. 检查Redis服务,是否已经启动
      -serve
    6. -
    -
    -

    其他默认即可,不要设置Memory Limit

    -
    -
  • -
-

构建项目

-

获取源代码

-
git clone git@github.com:thx/RAP.git
git checkout release
-
-

确保您正确的切换到release分支,否则会出现少包,因为master分支引用一些不对外公开的内部组件,不提供给外部用户使用

-
-

导入到IDEA

-

IDEA==>Open==>RAP

-

初始化数据库

-

执行脚本文件:RAP\src\main\resources\database\initialize.sql

-

修改配置文件

-

文件:RAP\src\main\resources\database\config.properties
-修改:数据库用户名密码
-update

-

启动项目

-
    -
  1. Edit config
    -config
  2. -
  3. Create Tomcat
    -create
  4. -
  5. Deploy war
    -deploy
  6. -
  7. Deploy success
    -success
  8. -
-

注意成功部署后,请注册新账号登录

-

至此,RAP1的本机部署已经完成。

-

其他

-
    -
  • RAP1学习中心
    -部分同学无法查看视频,请异步至issues
  • -
  • RAP1 Wiki文档
  • -
  • Mockjs
  • -
  • RAP2环境搭建教程
  • -
-]]>
- - Api - - - RAP - -
- - Api 文档管理系统 RAP2环境搭建 - /2018/03/27/rap2/ - RAP2是采用前后端分离的形式,因此搭建完整的RAP2需要 服务端:rap2-delos客户端:rap2-dolores 同时部署

-

部署RAP2需要亲具有Node+Linux+MySQL的运维知识,如果亲对此不是很了解,建议用http://rap2.taobao.org 线上版本就可以

-

由于 客户端:rap2-dolores 是建立在 服务端:rap2-delos 基础上,因此先搭建服务端应用

- -

个人贡献 📖 issues 119

-
    -
  • 截至 2018-08-01 delos 并没有发布 Tag版本,应该还处于功能开发前期阶段吧。本教程是在CentOS机器上实战部署
  • -
  • 然而安装部署并不是顺利,因此记录踩过的坑(别问我为啥不用Docker,因为我司分配的机器无法满足Docker的最低内核版本),安装环境介绍:Redis,delos,dolores均在一台服务器,MySQL使用已存在的服务
  • -
  • 本篇文章最后更新于 2018-08-01,因此后续的项目部署相关,请参考官方部署教程
  • -
-
-

安装基本工具

-
    -
  • Git
  • -
  • Node 8.9.4+
  • -
  • Redis 4.0+
  • -
  • MySQL 5.7+
  • -
-

以上基本工具请根据自身需要,下载对应系统安装包,请自行解决安装配置等问题,这里不做过多说明

-
-

Redis 安装可参考Linux 常用应用安装
-Redis 最好用非安全模式启动

-
-

服务端delos环境搭建

-

构建项目

-
-

构建项目前,请确认Node,Redis,MySQL服务均能正常使用

-
-
git clone https://github.com/thx/rap2-delos.git
-

环境配置

-

创建数据库

-
    -
  • -

    Mac or Linux

    -
    mysql -e 'CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP DEFAULT CHARSET utf8 COLLATE utf8_general_ci';
    -
  • -
  • -

    Windows 环境

    -

    进入mysql命令后执行

    -
    CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
    -
  • -
-

配置文件

-

目录:rap2-delos/src/config
-文件:config.dev.ts;其中dev,表示开发环境,其他同理
-修改:config.dev.ts文件中db对象中usernamepassword参数与本地或者开发环境的数据库信息匹配

-

启动项目

-

安装项目依赖包

-

项目根目录下执行

-
# 安装项目所需依赖
npm install
# 全局安装PM2
npm install -g pm2
-

安装TypeScript编译包

-
npm install typescript -g
-
-

如果下载缓慢,请使用淘宝npm镜像

-
-

初始化数据库

-

项目根目录下执行(该过程比较慢,耐心等待初始化完成)

-
npm run create-db
-

编译启动项目

-

执行mocha测试用例和js代码规范检查

-
npm run check
-
    -
  • 开发模式
    -启动开发模式的服务器 监视并在发生代码变更时自动重启(第一次运行比较慢,请耐心等待)
    npm run dev
    -
  • -
  • 生产模式
    -启动生产模式服务器
    npm start
    -
  • -
-

看到浏览器中如下提示,表示服务端delos已经部署成功

-
-

RAP2后端服务已启动,请从前端服务(rap2-dolores)访问。 RAP2 back-end server is started, please visit via front-end service (rap2-dolores).

-
-

或者在程序控制台出现如下Log,表示服务端delos已经部署成功
-delos

-

常见问题

-

部署问题

-
    -
  1. -

    Windows下执行 npm run build,提示'rm' 不是内部或外部命令,也不是可运行的程序或批处理文件

    -

    原因:rm 是Linux下命令,
    -解决方法:Windows系统可使用 git bash 打开该项目,执行该命令

    -
  2. -
  3. -

    执行 npm run create-db 命令,提示
    -Unable to connect to the database:{ SequelizeAccessDeniedError: Access denied for user 'root'@'localhost' (using password:NO)}

    -

    原因:未修改 rap2-delos/src/config 目录下数据库配置文件,或者是与文件中的数据库信息与之连接的数据库信息不匹配
    -解决方法:修改 config.dev.ts 文件数据库配置信息

    -
    -

    如果修改正确无误后,执行 npm run create-db 依旧出错,那么查看该项目中是否已经存在 dist 目录,如果有,请按照如上修改对应的数据库配置信息

    -
    -
  4. -
  5. -

    执行 npm run dev 命令,提示 Error: listen EADDRINUSE :::8080
    -原因:8080端口被占用
    -解决方法:杀掉占用8080端口的应用

    -
  6. -
  7. -

    执行 npm install 命令,提示 hiredis 编译无法通过
    -原因:无权限操作rap2-delos/node_modules/hiredis路径
    -解决方法:sudo npm install

    -
    -

    如果提示sudo: npm: command not found,请参考 stackoverflow-npmstackoverflow-node

    -
    -
  8. -
  9. -

    执行 npm run dev 可以正常启动,npm start 命令无法正常启动服务
    -原因:请使用 pm2 logs 查看日志具体定位
    -示例:由于Redis的安全模式,不能正常使用

    -
    ReplyError: Ready check failed: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 

    1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent.
    2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server.
    3) If you started the server manually just for testing, restart it with the '--protected-mode no' option.
    4) Setup a bind address or an authentication password.
    NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.
    -

    解决方法: 使用--protected-mode no方式启动

    -
  10. -
-

客户端dolores环境搭建

-

构建项目

-

获取源代码

-
git clone https://github.com/thx/rap2-dolores.git
-

环境配置

-

配置文件

-

目录:rap2-dolores/src/config
-文件:config.dev.ts;其中dev,表示开发环境,其他同理
-修改:config.dev.ts 配置文件 serve 的地址,更改为 服务端rap2-delos)部署成功后的地址,默认:'http://localhost:8080'

-

启动项目

-

安装项目依赖包

-

项目根目录下执行

-
npm install
-
-

如果下载缓慢,请使用淘宝npm镜像

-
-

编译启动项目

-
    -
  • 开发模式
    -自动监视改变后重新编译
    npm run dev
    -备注:测试用例
    npm run test
    -
  • -
  • 生产模式
    -编译React生产包
    npm run build
    -用serve命令或nginx服务器路由到编译产出的build文件夹作为静态服务器即可
    serve -s ./build -p 80
    -
  • -
-

看到浏览器中出现登录页面,表示部署成功
-dolores

-

常见问题

-

部署问题

-
    -
  1. -

    执行npm run dev,提示

    -
    return process.dlopen(module,path._makeLong(filename))
    ...
    ...node_modules\node-sass\vendor\win32-x64-57\binding.node is not a valid Win32 application...
    -

    原因:项目依赖包node-sass没有安装完全
    -解决方法:npm install node-sass

    -
  2. -
  3. -

    项目运行起来,但一直停留在加载动画那里

    -

    浏览器控制台输出:

    -
    GET http://127.0.0.1:8080/account/info  ==>>
    Failed to load http://127.0.0.1:8080/account/info
    -

    原因:未修改rap2-delos/src/config目录下服务端连接地址,或者修改结果与rap2-dolores实际提供服务地址不匹配
    -解决方法:修改config.dev.ts文件serve配置信息

    -
    -

    如果Windows系统修改正确无误后,依旧出错,查看hosts(路径:C:\Windows\System32\drivers\etc)中127.0.0.1的IP前是否有#,如果有请取消注释

    -
    -
  4. -
-

其他

-

MySQL 运行问题

-
    -
  • 错误一
    -mysql
    -原因:MySQL 集成命令没有加入系统的环境变量
    -解决方法:将安装的MySQL Service路径加入系统变量
    -path
  • -
  • 错误二
    -create
    -原因:没有数据库链接权限
    -解决方法:先登录用root数据库,密码具体看自己数据库当时设置的密码
  • -
-

如何获取更新

-

目前请选择 master 分支源码,后续其他分支请看相应分支说明文档。在开发环境中git pull来获取最新的源码更新,每一期更新都会有对应的update.md请关注并按照上面的指示进行升级工作。

-

附录

-
    -
  • Redis如何后台启动
  • -
  • Redis配置文件介绍
  • -
  • PM2实用入门指南
  • -
-]]>
- - Api - - - RAP - -
- - Realm 数据库快速上手 - /2018/04/24/realm/ - realm-db

- -

Android 供了多种选项来保存永久性应用数据。

-
    -
  • Shared preferences
  • -
  • Internal file storage
  • -
  • External file storage
  • -
  • Databases
  • -
  • Network
  • -
-

其中数据库存储是一种必备技能,而衍生的mobile db也是层出不穷,本节主要介绍全平台(除Android,iOS,macOS外还支持web,桌面应用)Realm数据库在Android上的使用

-

快速上手

-
    -
  • Android Studio 1.5.1+
  • -
  • JDK1.7+
  • -
  • Android API 9+
  • -
  • Realm 默认情况下使用内部存储(internal storage),一般来说,这个文件位于/data/data/<packagename>/files/,文件名:default.realm
  • -
-

集成

-
    -
  • -

    在项目的 build.gradle 文件中添加如下 class path 依赖

    -
    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    classpath "io.realm:realm-gradle-plugin:5.0.0"
    }
    }
    -
  • -
  • -

    在 app 的 build.gradle 文件中应用 realm-android 插件

    -
    apply plugin: 'realm-android'
    -
  • -
-

初始化

-
    -
  • -

    默认初始化

    -
    public class MyApplication extends Application {
    @Override
    public void onCreate() {
    super.onCreate();
    // 默认Realm的配置文件
    Realm.init(this);
    }
    }
    -
  • -
  • -

    自定义初始化

    -
    public class MyApplication extends Application {
    @Override
    public void onCreate() {
    super.onCreate();
    // 自定义配置Realm
    initRealm();
    }

    private void initRealm() {
    RealmConfiguration config = new RealmConfiguration.Builder()
    .name("myrealm.realm") // 命名文件名:myrealm.realm
    .inMemory() // 一个非持久化的、存在于内存中的 Realm 实例
    .encryptionKey(getKey()) // 数据库加密key
    .schemaVersion(2) // 数据库结构版本号
    .modules(new MySchemaModule()) // 数据库结构对象
    .migration(new MyMigration()) // 数据库迁移
    .build();
    Realm.setDefaultConfiguration(config);
    }
    }
    -
    -
      -
    1. Realm 实例是线程单例化的,也就是说多次在同一线程调用静态构建器会返回同一 Realm 实例
    2. -
    3. 使用同样的名称同时创建“内存中的”Realm 和常规的(持久化)Realm 是不允许的
    4. -
    -
    -
  • -
-

字段类型

-

Realm 支持以下字段类型:booleanbyteshortintlongfloatdoubleStringDatebyte []。整数类型 shortintlong 都被映射到 Realm 内的相同类型(实际上为 long )。

-
    -
  • @Required修饰类型和空值(null) -
    -

    Realm强制禁止空值(null)被存储
    -只有Boolean,Byte,Short,Integer,Long,Float,Double,String,byte[],Date可被修饰

    -
    -
  • -
  • @Ignore标识一个字段不应该被保存到 Realm
  • -
  • @Index为字段增加搜索索引 -
    -

    仅支持索引的属性类型包括:String,byte,short,int,long,booleanDate

    -
    -
  • -
  • @PrimaryKey -
    -

    必须为字符串(String)或整数(short,int,long)以及它们的包装类型(Short,Int,Long

    -
    -
  • -
-

声明Realm数据模型

-

RealmObject

-

可以把RealmObject 当作POJO使用

-
public class User extends RealmObject {

}
-

RealmModel

-
@RealmClass
public class User implements RealmModel {

}
-

关系

-

多对一

-
public class Contact extends RealmObject {
private Email email;
// Other fields…
}

public class Email extends RealmObject {
private String address;
private boolean active;
// ... setters and getters left out
}
-

多对多

-
public class Contact extends RealmObject {
public String name;
public RealmList<Email> emails;
}

public class Email extends RealmObject {
public String address;
public boolean active;
}
-

CRUD

-
    -
  • 所有的写操作(添加、修改和删除对象),必须包含在写入事务(transaction)中
  • -
  • 在提交期间,所有更改都将被写入磁盘,并且,只有当所有更改可以被持久化时,提交才会成功。通过取消一个写入事务,所有更改将被丢弃。
  • -
  • 益于 Realm 的 MVCC 架构,当正在进行一个写入事务时读取操作并不会被阻塞!这意味着,除非你需要从多个线程进行并发写入操作,否则,你可以尽量使用更大的写入事务来做更多的事情而不是使用多个更小的写入事务。
  • -
-

-
    -
  • -

    事务执行

    -
    Realm realm = Realm.getDefaultInstance();
    realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
    User user = realm.createObject(User.class);
    user.setName("John");
    user.setEmail("john@corporation.com");
    }
    });
    -
  • -
  • -

    异步事务

    -
    Realm realm = Realm.getDefaultInstance();
    realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm bgRealm) {
    User user = bgRealm.createObject(User.class);
    user.setName("John");
    user.setEmail("john@corporation.com");
    }
    }, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
    // Transaction was a success.
    }
    }, new Realm.Transaction.OnError() {
    @Override
    public void onError(Throwable error) {
    // Transaction failed and was automatically canceled.
    }
    });
    -
    -

    OnSuccess 和 OnError 并不是必须重载的,重载了的回调函数会在事务成功或者失败时在被调用发生的线程执行。

    -
    -
  • -
-

-

-

-

Realm进阶

-

Realm云

-]]>
- - DataBase - Realm - - - Realm - -
- - RxJava 入门 - /2018/10/02/rxjava/ - RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.(一个在 Java VM 上使用可观测的序列( 观察者模式 )来组成异步的、基于事件的程序的库).

-

在实际开发过程中,RxJava已是一个不可或缺的组件,因此对于RxJava的学习和思考,记录分享是很重要的一个环节

-

本系列文章主要:

-
    -
  1. RxJava 入门
  2. -
  3. RxJava 实际应用
  4. -
  5. RxJava 源码剖析
  6. -
- -

目前来说,RxJava有两个版本,RxJava1 与 RxJava2 两个版本之间虽然存在很多不同,但它们的本质是相同,由于对于RxJava1 已废弃,因此建议没有学习或者是使用过,可直接上手学习RxJava2(在学习过程中部分地方还是会有RxJava1相关的说明,但这不是重点)

-

文章使用RxJava版本如下:

-
    -
  • implementation 'io.reactivex:rxjava:1.3.0'
  • -
  • implementation 'io.reactivex.rxjava2:rxjava:2.2.1'
  • -
  • 项目示例:rc-cluster-network
  • -
-
-

由于一个项目中RxJava1与RxJava2并不能共存,因此实际参考项目中仅RxJava2示例

-
-

RxJava 基础

-

RxJava1 VS RxJava2

-

rxjava1 vs rxjava2

-
-

以上是列举出不同版本间主要的变换,其它更细节部分,请查看官方Wiki

-
-

关键词

-

RxJava1

-
    -
  • Observable (可观察者,即被观察者)
  • -
  • Observer (观察者)
  • -
  • subscribe (订阅)
  • -
  • 事件
  • -
-
-

Observable和Observer通过subscribe()方法实现订阅关系,从而Observable可以在需要的时候发出事件来通知Observer

-
-

RxJava2

-
    -
  • Observable (可观察者,即被观察者)
  • -
  • Observer (观察者)
  • -
  • ObservableEmitter (发射器)
  • -
  • 事件
  • -
-
-

RxJava2中SubscrberObservableEmitter取代,Observer中多了一个回调方法 onSubscribe(),传递参数为Disposable

-
-
    -
  • ObservableEmitter:Emitter是发射器的意思,这个就是用来发出事件,它可以发出三种类型的事件,通过调用emitteronNext(T value)onComplete()onError(Throwable e)就可以分别发出next事件,complete事件和error事件
  • -
  • Disposable:字面意思是一次性用品,用完即可丢弃。在RxJava中可以理解成两根管道间的阀门,当调用它的的dispose()方法时,它就将两根管道切断,从而导致下游收不到事件,即相当于Subsciption
  • -
-

基本实现

-

Create Observable

-

operators

-
RxJava1
-
Observable<String> observable = Observable.unsafeCreate(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("World");
subscriber.onNext("RxJava1");
subscriber.onCompleted();
}
});
-
-

1.2.7版本后,Observable的create()方法已被废弃,如果没有特殊需求,可以使用unsafeCreate()代替,构造Obaservable实例

-
-
RxJava2
-
Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext("Hello");
emitter.onNext("World");
emitter.onNext("RxJava2");
emitter.onComplete();
}
});
-

unsafeCreate()/create()方法是RxJava最基本创建时间序列的方法。基于这个方法,RxJava还提供了一些方法来快捷创建事件队列

-
    -
  • just(T…):将传入的参数依次发送出来
    Observable observable = Observable.just("Hello", "World", "RxJava");
    // 将会依次调用:
    // onNext("Hello");
    // onNext("World");
    // onNext("RxJava");
    // onCompleted();
    -
  • -
  • from(T[])/from(Iierabble<? extends T>):将传入的数组或Iterable拆分成具体对象后,依次发送出来
    String[] words = {"Hello", "World", "RxJava"};
    Observable observable = Observable.from(words);
    // 将会依次调用:
    // onNext("Hello");
    // onNext("World");
    // onNext("RxJava");
    // onCompleted();
    -
  • -
-
Flowable
-

Flowable是RxJava2中新增的类,专门应对背压(Backpressure)问题,但这个概念并不是RxJava2中引入的概念。

-

出现Flowable的原因:即生产者(被观察者发送事件)的速度与消费者(观察者接收所有事件)的速度不匹配,从而导致观察者无法及时响应/处理所有发送过来的事件问题,最终导致缓冲区溢出,事件丢失 & OOM等问题。

-

一般情况,被观察者发送事件速度 > 观察者接收事件速度。比如:点击过快造成等

-
Flowable.create(new FlowableOnSubscribe<String>() {

@Override
public void subscribe(FlowableEmitter<String> emitter) throws Exception {
emitter.onNext("Hello");
emitter.onNext("World");
emitter.onNext("RxJava2");
emitter.onComplete();
}
}, BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.newThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
// 相当于onNext
Thread.sleep(1000);
System.out.println("accept");
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
// 相当于onError
System.out.println("accept" + throwable.toString());
}
});
-

Flowable并不是订阅就开始发送数据,而是需等到执行Subscription.request()才开始发送数据

-

Create Observer

-
RxJava1
-
Observer<String> observer = new Observer<String>() {
@Override
public void onCompleted() {
System.out.println("Completed!");
}

@Override
public void onError(Throwable e) {
System.out.println("Error" + e);
}

@Override
public void onNext(String s) {
System.out.println("Next" + s);
}
};
-

除了Observer接口之外,RxJava内置了一个实现Observer的抽象类SubscriberSubscriberObserver接口进行了一些扩展,但它们的基本使用方式是完全一样

-
Subscriber<String> subscriber = new Subscriber<String>() {
@Override
public void onCompleted() {
System.out.println("Completed!");
}

@Override
public void onError(Throwable e) {
System.out.println("Error" + e);
}

@Override
public void onNext(String s) {
System.out.println("Next" + s);
}
};
-

实际,在RxJava的subscribe过程中,Observer也总是会先被转成一个Subscriber再使用。对于使用者来说ObserverSubscriber的主要区别是:

-
    -
  1. onStart():这是Subscriber增加的方法。它会再subscribe刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如:数据的重置等操作。这是一个可选方法,默认情况下它的实现为空。
  2. -
-
-

注意:
-对于准备工作有线程要求,onStart()就不适用,因为它总是再subscribe所发生的线程被调用,而不能指定线程。要指定线程来准备工作,可以使用doOnSubscribe()方法

-
-
    -
  1. unsubscribe():Subscriber所实现的另一个接口Subscription的方法,用于取消订阅。在这个方法被调用后,Subscriber将不再接收事件。
  2. -
-
-

注意:

-
    -
  • 一般需要在调用unsubscribe()方法前,需要使用isUnsubscribed()先判断状态。
  • -
  • 不再使用的时候尽快在合适的地方调用unsubscribe()来解除引用关系,以避免内存泄漏
  • -
-
-
RxJava2
-
Observer<String> observer = new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
System.out.println("Subscribe: " + d);
}

@Override
public void onNext(String s) {
System.out.println("Next: " + s);
}

@Override
public void onError(Throwable e) {
System.out.println("Error: " + e);
}

@Override
public void onComplete() {
System.out.println("Complete !");
}
};
-

Subscribe

-

创建好ObservableObserver之后,再用subscribe()方法将它们联结起来

-
observable.subscribe(observer);
// 或者(仅支持RxJava1)
observable.subscribe(subscriber);
-

chain calls

-

以上三步是使用RxJava进行异步操作的基本过程,创建被观察者,创建观察者被观察者订阅观察者,我们可以通过链式调用形式完成操作

-
Observable.unsafeCreate(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("World");
subscriber.onNext("RxJava1");
subscriber.onCompleted();
}
}).subscribe(new Observer<String>() {
@Override
public void onCompleted() {
System.out.println("Completed!");
}

@Override
public void onError(Throwable e) {
System.out.println("Error" + e);
}

@Override
public void onNext(String s) {
System.out.println("Next" + s);
}
});
-

简化订阅

-

除了subscribe(Observer)subscribe(Subscriber)(仅支持RxJava1)subscribe()还支持不完整的简化订阅回调

-
// RxJava1
Action1<String> onNextAction = new Action1<String>() {
@Override
public void call(String s) {
System.out.println("onNext" + s);
}
};

Action1<Throwable> onErrorAction = new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
System.out.println("onError" + throwable);
}
};

Action0 onCompletedAction = new Action0() {
@Override
public void call() {
System.out.println("completed");
}
};

// RxJava2
Consumer<String> onNextAction = new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
System.out.println("onNext" + s);
}
};

Consumer<Throwable> onErrorAction = new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
System.out.println("onError" + throwable);
}
};

Action onCompleteAction = new Action() {
@Override
public void run() throws Exception {
System.out.println("complete");
}
};

// 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext()
observable.subscribe(onNextAction);
// 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和 onError()
observable.subscribe(onNextAction, onErrorAction);
// 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来定义 onNext()、 onError() 和 onCompleted()
observable.subscribe(onNextAction, onErrorAction, onCompletedAction/onCompleteAction);

-

RxJava 线程

-

在RxJava的默认规则中,事件的发出和消费都是在同一个线程(在哪个线程条用subscriber(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件),也就是说,以上RxJava基本操作,实现出来的只是一个同步的观察者模式。而观察者模式本身的目的是“后台处理,前台回调”的异步机制,因此在RxJava中通过Scheduler来对线程进行管理

-

Scheduler API

-

Scheduler相当于线程控制器,RxJava通过它指定代码应该运行在什么样的线程,其中RxJava中内置了几个Scheduler

-
    -
  • Schedulers.computation():计算所使用的Scheduler。这个计算值的是CPU密集型计算,即不会被I/O操作等限制性能的操作。不要把I/O操作放在computation()中,否则I/O操作的等待时间会浪费CPU。
  • -
  • Schedulers.form(Executor):
  • -
  • Schedulers.immediate():直接在当前线程运行,相当于不指定线程,这也是默认的Scheduler.
  • -
  • Schedulers.io():I/O操作(读写文件,读写数据库,网络信息交换等)所使用的Scheduler。行为模式和newThread()差不多,区别在于io()的内部实现是一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下io()比newThread()更高效
  • -
  • Schedulers.newThread():总是启用新线程,并在新线程执行操作
  • -
  • Schedulers.single()『仅RxJava2中存在』:
  • -
  • Schedulers.test()『仅RxJava1中存在』:顾名思义,这是一个测试
  • -
  • Schedulers.trampoline():
  • -
  • AndoroidSchedulers.mainThread():指定操作在Android的主线程
  • -
-

有了Scheduler,我们可以使用subscribeOn()observeOn()方法来对线程进行控制

-
    -
  • -

    subscribeOn():指定subscribe()所发生的线程,即Observable.OnSubscribe被激活时所处的线程,或者叫做事件的产生的线程

    -
  • -
  • -

    observeOn():指定Subscriber所运行的线程。或者叫做事件的消费线程

    -
    // RxJava2
    Observable.create(new ObservableOnSubscribe<String>() {
    @Override
    public void subscribe(ObservableEmitter<String> emitter) throws Exception {
    emitter.onNext("Hello");
    emitter.onNext("World");
    emitter.onNext("RxJava2");
    emitter.onComplete();
    }
    })
    // 指定 subscribe() 发生在 IO 线程
    .subscribeOn(Schedulers.io())
    // 指定 Subscriber 的回调发生在主线程
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Consumer<String>() {
    @Override
    public void accept(String s) throws Exception {
    System.out.println(s);
    }
    });
    -
  • -
-

操作符

-

说RxJava好用,还有一个原因是RxJava提供了大量的操作符,这些操作符保证了在面都复杂的逻辑下,依旧可以是逻辑清晰的链式调用

-

RxJava_action

-

总结

-

本篇文章作为RxJava系列的学习的入门,不会讲解相关操作的原理等
-学习目的

-
    -
  • 了解RxJava1与RxJava2之间的不同点,
  • -
  • 了解RxJava的线程管理,
  • -
  • 掌握完成RxJava的基本操作,
  • -
  • 清楚RxJava操作符,以及分别适用于什么样的场景
  • -
-

附录

-

文章中部分原话引用了参考学习文章的原话,在这里向那些无私分享的大佬致敬

-
    -
  • 给 Android 开发者的 RxJava 详解
  • -
  • RxJava系列教程
  • -
-]]>
- - RxJava - - - RxJava - -
- - 琅嬛福地 - /2021/03/06/scenically/ - 在金庸武侠《天龙八部》中,“琅嬛福地”存放了无崖子和李秋水搜罗天下各门各派的武功,江湖人士练成这里的一门武功绝学,就能在江湖中有自己的一席之地。而这里存放了我计算机相关学习、实践应用,以及经常使用的一些网站资源

- -

计算机网络

- -

数据结构与算法

- -

操作系统

- -

计算机组成原理

- -

编译原理

- -

资源

- -

参考

-
    -
  1. 聊一聊我在B站上自学编程的经历吧
  2. -
-]]>
- - Resources - - - DevTool - -
- - 非对称加密——RSA - /2018/07/03/security-rsa/ - 这是常用加密技术的系列文章,主要包含非对称对称JWT三类常用技术的应用

-

RSA

-

RSA:RSA加密算法是一种 非对称 加密算法。在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。RSA就是他们三人姓氏开头字母拼在一起组成的。

- -

RSA加密解密

-

公钥 加密 私钥 解密,持有公钥(多人持有,客户端)可以对数据加密,但是只有持有私钥(一人持有,服务端)才可以解密并查看数据

-

RSA加签验签

-

私钥 加签 公钥 验签,持有私钥(一人持有,服务端)可以加签,持有公钥(多人持有,客户端)可以验签

-

RSA过程示意图

-

security-rsa

-

如上图,具体表述两个场景过程

-

结果不需加密

-

场景:返回的数据不需要加密(例如:绑定银行卡的时候)

-
    -
  • 客户端Client A发送使用服务端Serve publicKey 加密 的密文cipher A(包含用户的银行卡号,手机号等重要信息)到服务器
  • -
  • 服务器Serve 通过 Serve privateKey解密
  • -
  • 服务端业务处理完成,直接返回数据(一些普通信息,比如状态码code,提示信息msg,提示操作是成功还是失败)给客户端Client A
  • -
-

结果需加密

-

场景:返回的数据需要加密(例如:用户登录)

-
    -
  • 客户端Client B发送使用服务端Serve publicKey 加密 的密文cipher B(包含用户名和密码等重要信息)以及客户端Client BClient B publicKey到服务器
  • -
  • 服务器Serve 通过 Serve privateKey解密
  • -
  • 服务端业务处理完成,直接返回数据(一般为token,token使用客户端Client BClient B publicKey加密)给客户端Client B
  • -
  • 客户端Client B使用Client B privateKey进行 解密 获取相应的用户信息等
  • -
-

密钥对

-

在使用RSA加密解密之前,首先要生成密钥对。所谓的密钥对,指的是公钥和私钥。RSA算法的密钥可以通过两个途径生成,一是借助openssl命令终端,二是使用JDK生成。
-本篇采用JDK方式生成密钥对,openssl方式可自行尝试

-

JDK

-

Serve端密钥对

-

Client端密钥对

-
Android密钥对
-
Web密钥对
-
iOS密钥对
-

OpenSSL

-

略…

-

RSA加密

-

RSA解密

-

RSA缺点

-

虽然RSA是一种较高级别加密机制,但也存在一些缺点

-
    -
  1. 产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密。
  2. -
  3. 安全性,RSA的安全性依赖于大数的因子分解,但并没有从理论上证明破译RSA的难度与大数分解难度等价,而且密码学界多数人士倾向于因子分解不是NP问题。
  4. -
  5. 速度太慢,由于RSA的分组长度太大,为保证安全性,n 至少也要 600 bit 以上,使运算代价很高,尤其是速度较慢,较对称密码算法慢几个数量级;且随着大数分解技术的发展,这个长度还在增加,不利于数据格式的标准化。
  6. -
-

附录

-

参考学习文章

-
    -
  • 一张图了解RSA加解密与加验签
  • -
  • RSA加密解密及RSA加签验签
  • -
  • RSA加解密和加签验签
  • -
  • RSA加密解密样例
  • -
  • RSA加密解密实现
  • -
-]]>
- - Security - - - RSA - -
- - 【译】• 面向服务的架构 - /2019/06/19/soa/ - 在学习过程中,我们首先需要将学习知识的基本概念搞清楚,而搞清楚概念最权威的方式是查阅 英文版 • 维基百科 ,或者是对应知识的官方文档上面查找相关的知识。这样学习才能学习到知识的精华,而不是阅读经过别人转译过的文章。因此,这篇文章仅是本人在学习 SOA 基本概念时,对维基百科知识的一个汇总翻译记录,不建议朋友把这篇文章当做你的学习资料,具体请查阅Service-oriented architecture

-

面向服务的架构(SOA)是一种软件设计风格。 SOA 服务通过应用组件,通过网络通信协议的方式向其他组件提供服务。SOA 的基本原则是独立于厂商,独立于产品以及独立于技术[1]。服务是一种功能的离散独立单元,可以远程访问并独立运行与更新,例如在线查询信用卡账单。

- -

一个服务在诸多 SOA 定义中有 4 个属性[2]

-
    -
  1. 它逻辑上代表具有指定结果的业务活动
  2. -
  3. 它是自包含的
  4. -
  5. 它对于消费者来说是黑盒(不可见)
  6. -
  7. 它可能包含其他基础服务[3]
  8. -
-

不同的服务可以联合起来构建大型的软件应用[4],SOA 遵循模块化编程思想,SOA 集成了分布式,独立维护和独立部署的软件组件,它通过技术和标准促使组件通过网络进行通信和协作,尤其是通过 IP 网络

-

概览

-

在 SOA 中,服务的使用是描述怎样通过元数据传递消息和解析消息的协议。该元数据描述了服务的功能特性和服务质量特征。SOA 目标旨在允许用户将大块的功能组合在一起来去构成一个应用,形成纯粹由现有服务构建并以临时方式组合的应用程序。一个服务会向调用者提供一个简单的接口,它抽象出作为黑盒子的底层复杂性。其他用户可以在不了解其内部实现的情况下访问这些独立服务[5]

-

定义概念

-

相关的流行语服务导向促进了面向服务间的松耦合。SOA 将功能分为不同的单元或服务,哪些开发人员可以通过网络访问,以便允许用户在应用程序的生产中组合和重用它们。这些服务及其相应的消费者通过明确定义的共享格式,传递数据或通过协调两个或更多服务之间的活动来互相通信

-

2009 年 10 月发布了关于 SOA 的宣言,其中提出了 6 个核心价值,如下所示

-
    -
  1. 商业价值比技术战略更重要
  2. -
  3. 战略目标比项目特定的利益更重要
  4. -
  5. 内在的互操作性比定制集成更重要
  6. -
  7. 共享服务比特定用途实现更重要
  8. -
  9. 灵活性比优化更重要
  10. -
  11. 演进式比追求初始化完美更重要
  12. -
-

SOA 可看作是连续统一体的一部分,其范围从旧的分布式计算概念和模块化编程[6] [7],通过 SOA,以及 mashupsSaaS云计算的当前实践(有些人认为是 SOA 的后代)[8]

-

原理

-

尽管许多行业已发布了自己的原则,但没有与 SOA 确切相关的行业标准,其中一些[9] [10] [11] [12]包括以下内容

-

标准地服务契约

-

服务遵循标准通信协议,有一组给定服务中的一个或多个服务描述文档共同定义

-

服务自治

-

服务之间的关系被最小化到它们只知道存在的等级

-

服务松耦合

-

无论网络位于何处,都可以从网络中的任何位置调用服务

-

服务长寿

-

服务应该设计为长寿,在可能的情况下,如果不需要新功能,服务应该避免强迫消费者进行更改。如果今天能调用的服务,到明天也应该能调用统一的服务

-

服务抽象化

-

服务充当黑盒,它们内在的逻辑对消费者是隐藏的

-

服务自治

-

服务是独立的,从设计时和运行时的角度控制它们封装的功能

-

服务无状态化

-

服务是无状态的,即返回请求的值或提供异常,从而最大限度的减少资源使用

-

服务粒度

-

确保服务具有足够的规模和范围的原则。服务向用户提供的功能必修三相关的

-

服务规范化

-

服务被分解或合并作为最小化冗余。在某些情况下,可能无法完成,这些是需要性能优化,访问和聚合的情况[13]

-

服务可组合性

-

服务可用于组成其他服务

-

服务发现

-

服务补充了交流元数据,通过它可以有效地发现和解释它们

-

服务可重用性

-

将逻辑分为多个服务,以促进代码的复用

-

服务封装

-

许多最初未在 SOA 下计划的服务可能会被封装或成为 SOA 的一部分

-

模式

-

每个SOA构建块都可以扮演以下三种角色中的任何一种:

-

服务提供者

-

它创建 Web 服务并将其信息提供给服务注册。每个提供者都会讨论大量的方法,以及为什么要公开哪些服务,哪些更重要:安全性或易用性,提供服务的价格等等。提供者还必须决定应该为给定的代理服务列出服务的类别[14]以及使用该服务需要那种协议

-

服务代理,服务注册或服务存储

-

其主要功能是使用任何潜在的请求者都能获取有关 Web 服务的信息。实施的人决定代理的范围。公开的代理随处可见,但私有的代理只能向有限的公开代理开放。UDDI 是一种早期的,不再主动支持的 Web 服务发现

-

服务消费者

-

它使用各种查找操作在代理注册中查找,然后绑定到服务提供者以调用其中一个 Web 服务。无论服务消费者需要哪种服务,它们都必须通过代理,将其与相应的服务绑定,然后使用它。如果服务提供多种服务,它们可以访问多种服务。

-

服务消费者-提供者关系由标准化服务契约[15]管理,其中包括业务部分,功能部分和技术部分。

-

服务组合模式有两种广泛的高级架构风格:服务编排。不受特定体系结构风格约束的较低级别的企业集成模式在 SOA 设计中任然具有相关性和合格性[16] [17] [18]

-

实现方法

-

SOA 可以通过 Web 服务[19]实现。这样做是为了使功能模块可以通过独立于平台和编程语言的标准协议访问。这些服务既可以代表新应用程序,也可以代表现有遗留系统的包装,使其具备网络功能。[20]

-

实现通用使用 Web 服务标准构建 SOA。一个例子是 SOAP,它在 2003 年从 W3C[21] 推荐 1.2 版本后获得广泛的行业认可。这些标准(也称为 Web 服务规范)还提供了更强的互操作性以及对锁定到专有供应商软件的一些保护。但是,也可以使用任何其他基于服务的技术(如 JiniCORBA 或者 REST)实现 SOA

-

架构可以独立于特定技术运行,因此可以使用多种技术实现,包括:

-
    -
  • 基于 WSDL 和 SOAPWeb 服务
  • -
  • 消息传递,例如,使用 ActiveMQ, JMS, RabbitMQ
  • -
  • RESTful HTTP,具有 Representational 状态转移(REST),构成自己的基于约束的架构风格
  • -
  • OPC-UA
  • -
  • WCF(Microsoft 的 Web 服务实现,构成 WCF 的一部分)
  • -
  • Apache Thrift
  • -
  • gRPC
  • -
  • SORCER
  • -
-

实现可以使用这些协议中的一个或多个,例如,可以使用文件系统机制来遵循符合 SOA 概念的进程间定义的接口规范来传递数据。关键是具有已定义接口的独立服务,可以调用它们以标准方式执行其任务,而无需预先知道调用应用程序的服务,并且没有应用程序具有或需要知道服务如何实际执行其任务。SOA 支持开发通过松耦合和可互操作的服务构建的应用程序

-

这些服务独立于底层平台和编程语言的正式定义(或契约,例如 WSDL)进行互操作。接口定义隐藏了实施特定语言的服务实现。因此,基于 SOA 的系统可以独立于开发技术和平台(例如Java,.NET等)运行。例如,运行在.NET平台上的 C# 和 用 JavaEE 平台上运行的 Java 编写的服务都可以公共复合应用程序(或客户端)使用。在任一平台上运行的应用程序也可以使用在另一个平台上运行的服务作为重用的 Web 服务。托管环境还可以包括 COBOL 遗留系统并将其作为软件服务提供。[22]

-

诸如 BPEL 之类的高级编程语言以及诸如 WS-CDLWS-Coordination 之类的规范通过提供一种定义和支持将细粒度服务编排成更粗粒度的业务服务的方法来扩展服务的概念,架构师可以将其合并到复合应用程序或门户中实现的工作流和业务流中。

-

面向服务的建模是一个 SOA 架构,可识别指导 SOA 从业者对其面向服务的资产进行概念化,分析,设计和构建的各种规程。面向服务的建模框架(SOMF)提供了一种建模语言和一个工作架构映射,描述了有助于成功的面向服务的建模方法的各种组件。它说明了识别服务开发方案的“做什么”方面的主要元素。该模型使从业者能够制定项目计划并确定面向服务的计划的里程碑。SOMF 还提供了一种通用的建模符号,已解决业务和 IT 组织之间的一致性问题。

-

组织利益

-

一些企业架构师认为,SOA 可以帮助企业更快,更经济地响应不断变化的市场条件。[23]这种体系结构促进了宏(服务)级别的重用,而不是微(类)级别的重用。它还可以简化现有 IT(传统)资产的互联和使用。

-

SOA的元素,由Dirk Krafzig,Karl Banke和Dirk Slama撰写

-

使用SOA,我们的想法是组织可以从整体上看待问题。企业拥有更多地整体控制权。从理论上讲,不会有大量的开发人员使用任何工具集让他们满意,但他们将编码为业务中设定标准。他们还可以开发企业级 SOA,封装面向业务的基础架构。SOA 被描述为汽车驾驶员提供效率的高速公路系统。关键在于,如果每个人都有车,但在任何地方都没有高速公路,那么事情就会受到限制和混乱,无论是视图快速或有效地到达任何地方。IBM Web 服务副总裁 Michael Liebow 表示 SOA 是“建设的高速公路”。[25]

-

SOA元模型,The Linthicum Group,2007

-

在某些方面,SOA可以被视为架构演变而不是革命。它捕获了以前软件架构的许多最佳实践。例如,在通信系统中,很少开发使用真正静态绑定与网络中的其他设备通信的解决方案。通过采用 SOA 方法,此类系统可以将自己定位为强调定义明确,高度可互操作的接口的重要性。SOA 的其他前身包括基于组件的软件工程和远程对象的面向对象分析和设计(OOAD),例如,在 CORBA 中。

-

服务包括仅通过正式定义的页面可用的独立功能单元。服务可以是某种易于生产和改进的“纳米企业”。服务也可以是作为子下属服务的协调工作而构建的“大型企业”。SOA 的成熟部署有效地定义了组织的 API。

-

将服务实施视为大型项目的单独项目的原因包括:

-
    -
  1. 分离将业务概念推广到业务,即服务可以快速独立地从组织中常见的较大且移动较慢的项目中提供。业务开始了解回调服务的系统和简化的用户界面。这提倡敏捷。也就是说,它促进了业务创新并加快了产品上市时间[26]
  2. -
  3. 分离促进了服务于消费项目的脱钩。这样可以鼓励良好的设计,因为服务的设计不需要知道消费者是谁。
  4. -
  5. 服务的文档和测试文件未嵌入较大项目的详细信息中。当服务需要在后续需要重用时,这很重要。
  6. -
-

SOA 承诺间接简化测试。服务是自治的,无状态的,具有完全记录的接口,并且与实现的关注点是分开的。如果组织拥有适当定义的测试数据,则会构建响应的存根,以便在构建服务时对测试数据做出反应。可以构建测试环境,其中原始和超出范围的服务是存根,而网格的其余ubuf 是完整服务的测试部署。由于每个接口都有完整的文档,并附有完整的回归测试文档,因此可以轻松识别测试服务中的问题。测试演变为仅仅验证测试服务是否根据其文档运行,并发现环境中所有服务的文档和测试用例存在的差距。管理幂等服务的数据状态是唯一的复杂性。

-

实例可能有助于将服务记录到有用的级别。Java community Process 中的一些 API 文档提供了良好的示例。由于这些是详尽无遗漏的,工作人员通常只使用重要的子集。JSR-89 的 ossjsa.pdf 文件中举例说明了这样做一个文件[27]

-

批评

-

SOA 已与 Web 服务混淆[28],但是,Web 服务只是实现构成 SOA 风格的模式的一种选择。在非本机或二进制形式的远程过程调用(RPC)的情况下,应用程序可能运行的更慢并且需要更多地处理能力,从而增加了成本。大多数实现都会产生这些开销,但 SOA 可以使用技术实现(例如,Java Business Integration(JBI)Windows Communication Foundation(WCF)data distribution service(DDS)),它们不依赖与远程过程调用或通过 XML 进行转换。与此同时,新兴的开源 XML 解析技术(如 VTD-XML)和各种 XML 兼容的二进制格式有望显著提高 SOA 性能。使用 JSON 而不是 XML 实现的服务不会受到这种性能的问题的影响[29] [30] [31]

-

有状态服务要求消费者和提供者共享相同的特定于消费者的上下文,该上下文包含在提供者和消费者之间交换的消息中或由其引入。如果服务提供者需要为每个消费者保留共享上下文,则此约束的缺点是它可能会降低服务提供者的整体可伸缩性。它还增加了服务提供者和消费者之间的耦合,使交换服务提供者更加困难[32]。最终,一些批评者认为 SOA 服务仍然受到他们所代表的应用程序的限制[33]

-

SOA 的体系结构面临的主要挑战是管理元数据。基于 SOA 的环境包括许多彼此之间进行通信以执行任务的服务。由于设计可能涉及多个服务一起工作,因此应用程序可能会产生数百万条消息。进一步的服务可能属于不同的组织甚至是竞争公司,造成巨大的信任问题。因此 SOA 治理进入了事务的计划[34]

-

SOA 面临的另一个主要问题是缺乏统一的测试架构。没有工具可以提供在 SOA 的体系结构中测试这些服务所需的功能。困难的主要原因是:[35]

-
    -
  • 异质性和解决方案的复杂性
  • -
  • 由于自主服务的集成,大量的测试组合
  • -
  • 包含来自不同和竞争供应商的服务
  • -
  • 由于新功能和服务的可用性,平台不断变化
  • -
-

请参阅 drops.dagstuhl.de[36],了解有关软件服务工程的其他挑战,部分解决方案和研究路线图

-

扩展和变体

-

事件驱动的体系结构

-

主要文章:事件驱动的架构

-

Web2.0

-

Tim O’Reilly 创造了“Web2.0”一词来描述一种快速增长的基于网络的应用程序[37]。经历了广泛报道的主题涉及 Web2.0与 SOA 体系机构之间的关系。

-

SOA 是将应用程序逻辑封装在具有统一定义的接口的服务中并通过发现机制公开可用的哲学。复杂性-隐藏和重用的概念,以及松耦合服务的概念,激发了研究人员详细阐述两种哲学,SOA 和 Web2.0及其各自应用之间的相似性。一些人认为 Web2.0和 SOA 具有显著不同的元素,因此不能被视为“平行哲学”,而其他人认为这两个概念是互补的,并将 Web2.0视为全球 SOA[38]

-

Web2.0和 SOA 的理念满足了不同的用户需求,从而暴露了设计方面的差异以及实际应用中使用的技术。但是,截止 2008 年,用例展示了结合 Web2.0和 SOA 技术和原则的潜力[38:1]

-

微服务

-

主要文章:微服务

-

微服务是对用于构建分布式软件系统的面向服务的体系结构的现代解释。微服务架构[39]中的服务是通过网络互相通信以实现目标的过程。这些服务使用技术不可知协议[40],这有助于封装语言和框架的选择,使他们的选择成为服务内部的一个问题。微服务是 SOA 的一种新的实现方法,自 2014 年(以及 DevOps 推出以后)开始流行,并且强调持续部署和其他的=敏捷实践[41]

-

微服务没有一个共同商定的定义。在文献中可以找到以下特征和原理:

-
    -
  • 细粒度接口(可独立部署的服务)
  • -
  • 业务驱动的开发(例如域驱动设计)
  • -
  • IDEAL 云应用架构
  • -
  • 多语言编程和持久化存储
  • -
  • 轻量级容器部署
  • -
  • 分散的持续交付
  • -
  • DevOps 提供全面的服务监控
  • -
-

其他

-
    -
  • 松耦合
  • -
  • OASIS SOA 参考模型
  • -
  • 服务粒度原则
  • -
  • SOA 治理
  • -
  • 软件架构
  • -
  • 面向服务的通信
  • -
  • 面向服务的应用程序开发
  • -
  • 面向服务的分布式应用程序
  • -
-

参考

-
-
-
    -
  1. Chapter 1: Service Oriented Architecture (SOA)”. msdn.microsoft.com. Archived from the original on February 6, 2016. Retrieved September 21, 2016. ↩︎

    -
  2. -
  3. Service-Oriented Architecture Standards - The Open Group”. www.opengroup.org. ↩︎

    -
  4. -
  5. What Is SOA?”. www.opengroup.org. Archived from the original on August 19, 2016. Retrieved September 21, 2016. ↩︎

    -
  6. -
  7. Velte, Anthony T. (2010). Cloud Computing: A Practical Approach. McGraw Hill. ISBN 978-0-07-162694-1. ↩︎

    -
  8. -
  9. Migrating to a service-oriented architecture, Part 1”. December 9, 2008. Archived from the original on December 9, 2008. Retrieved September 21, 2016. ↩︎

    -
  10. -
  11. Michael Bell (2008). “Introduction to Service-Oriented Modeling”. Service-Oriented Modeling: Service Analysis, Design, and Architecture. Wiley & Sons. p. 3. ISBN 978-0-470-14111-3. ↩︎

    -
  12. -
  13. Thomas Erl (June 2005). About the Principles. Serviceorientation.org ↩︎

    -
  14. -
  15. Application Platform Strategies Blog: SOA is Dead; Long Live Services”. Apsblog.burtongroup.com. January 5, 2009. Retrieved August 13, 2012. ↩︎

    -
  16. -
  17. Yvonne Balzer Improve your SOA project plans, IBM, July 16, 2004 ↩︎

    -
  18. -
  19. Microsoft Windows Communication Foundation team (2012). “Principles of Service Oriented Design”. msdn.microsoft.com. Retrieved September 3, 2012. ↩︎

    -
  20. -
  21. Principles by Thomas Erl of SOA Systems Inc. eight specific service-orientation principles ↩︎

    -
  22. -
  23. M. Hadi Valipour; Bavar AmirZafari; Kh. Niki Maleki; Negin Daneshpour (2009). “A brief survey of software architecture concepts and service oriented architecture”. 2009 2nd IEEE International Conference on Computer Science and Information Technology. pp. 34–38. doi:10.1109/ICCSIT.2009.5235004. ISBN 978-1-4244-4519-6. ↩︎

    -
  24. -
  25. Tony Shan (2004). “Building a service-oriented e Banking platform”. IEEE International Conference on Services Computing, 2004. (SCC 2004). Proceedings. 2004. pp. 237–244. doi:10.1109/SCC.2004.1358011. ISBN 978-0-7695-2225-8.2004 ↩︎

    -
  26. -
  27. Duan, Yucong; Narendra, Nanjangud; Du, Wencai; Wang, Yongzhi; Zhou, Nianjun. “Exploring Cloud Service Brokering from an Interface Perspective”. IEEE. ↩︎

    -
  28. -
  29. Duan, Yucong. “A Survey on Service Contract”. IEEE. ↩︎

    -
  30. -
  31. Olaf Zimmermann, Cesare Pautasso, Gregor Hohpe, Bobby Woolf (2016). “A Decade of Enterprise Integration Patterns”. IEEE Software. 33 (1): 13–19. doi:10.1109/MS.2016.11. ↩︎

    -
  32. -
  33. Rotem-Gal-Oz, Arnon (2012). SOA Patterns. Manning Publications. ISBN 978-1933988269. ↩︎

    -
  34. -
  35. K. Julisch et al., Compliance by Design – Bridging the Chasm between Auditors and IT Architects. Computers & Security, Elsevier. Volume 30, Issue 6-7, Sep.-Oct. 2011. ↩︎

    -
  36. -
  37. Brandner, M., Craes, M., Oellermann, F., Zimmermann, O., Web Services-Oriented Architecture in Production in the Finance Industry, Informatik-Spektrum 02/2004, Springer-Verlag, 2004 ↩︎

    -
  38. -
  39. www.ibm.com”. Retrieved September 10, 2016. ↩︎

    -
  40. -
  41. SOAP Version 1.2 の公開について (W3C 勧告)” (in Japanese). W3.org. Retrieved August 13, 2012. ↩︎

    -
  42. -
  43. Okishima, Haruhiru (2006). “. “Case Study of System Architecture that use COBOL assets”” (PDF). ↩︎

    -
  44. -
  45. Christopher Koch A New Blueprint For The Enterprise, CIO Magazine, March 1, 2005 ↩︎

    -
  46. -
  47. Enterprise SOA. Prentice Hall, 2005 ↩︎

    -
  48. -
  49. Elizabeth Millard (January 2005). “Building a Better Process”. Computer User. Page 20. ↩︎

    -
  50. -
  51. Brayan Zimmerli (November 11, 2009) Business Benefits of SOA, University of Applied Science of Northwestern Switzerland, School of Business ↩︎

    -
  52. -
  53. JSR-000089 OSS Service Activation API Specification 1.0 Final Release. sun.com ↩︎

    -
  54. -
  55. Joe McKendrick. “Bray: SOA too complex; ‘just vendor BS’”. ZDNet. ↩︎

    -
  56. -
  57. Jimmy Zhang (February 20, 2008) “Index XML Documents with VTD-XML”. XML Journal. ↩︎

    -
  58. -
  59. Jimmy Zhang (August 5, 2008) “i-Technology Viewpoint: The Performance Woe of Binary XML”. Microservices Journal. ↩︎

    -
  60. -
  61. Jimmy Zhang (January 9, 2008) “Manipulate XML Content the Ximple Way”. devx.com. ↩︎

    -
  62. -
  63. The Reason SOA Isn’t Delivering Sustainable Software”. jpmorgenthal.com. June 19, 2009. Retrieved June 27, 2009. ↩︎

    -
  64. -
  65. SOA services still too constrained by applications they represent”. zdnet.com. June 27, 2009. Retrieved June 27, 2009. ↩︎

    -
  66. -
  67. Governance Layer”. www.opengroup.org. Retrieved September 22, 2016. ↩︎

    -
  68. -
  69. How to Efficiently Test Service Oriented Architecture | WSO2 Inc”. wso2.com. Retrieved September 22, 2016. ↩︎

    -
  70. -
  71. http://drops.dagstuhl.de/opus/volltexte/2009/2046/pdf/09021_abstracts_collection.2046.pdf ↩︎

    -
  72. -
  73. What Is Web 2.0”. Tim O’Reilly. September 30, 2005. Retrieved June 10, 2008. ↩︎

    -
  74. -
  75. Christoph Schroth & Till Janner (2007). “Web 2.0 and SOA: Converging Concepts Enabling the Internet of Services”. IT Professional 9 (2007), Nr. 3, pp. 36–41, IEEE Computer Society. Retrieved February 23, 2008. ↩︎ ↩︎

    -
  76. -
  77. Dragoni, Nicola; Giallorenzo, Saverio; Alberto Lluch Lafuente; Mazzara, Manuel; Montesi, Fabrizio; Mustafin, Ruslan; Safina, Larisa (2016). “Microservices: yesterday, today, and tomorrow”. arXiv:1606.04036v1 cs.SE. ↩︎

    -
  78. -
  79. James Lewis and Martin Fowler. “Microservices”. ↩︎

    -
  80. -
  81. Balalaie, A.; Heydarnoori, A.; Jamshidi, P. (May 1, 2016). “Microservices Architecture Enables DevOps: Migration to a Cloud-Native Architecture”. IEEE Software. 33 (3): 42–52. doi:10.1109/MS.2016.64. hdl:10044/1/40557. ISSN 0740-7459. ↩︎

    -
  82. -
-
-]]>
- - Translation - SpringBoot - - - SpringBoot - SOA - -
- - SpringBoot(一) 初识 - /2019/06/23/springboot1/ -

- -

从本篇文章开始,记录学习 SpringBoot 框架在实践,源码方面的知识,本节是第一篇,因此不涉及相关复杂知识的学习。众所周知,随着微服务的广泛流行,Spring 系列的 SpringBoot 和 SpringCloud 的应用也更受欢迎,那么请跟随我的脚本来一步步解开 SpringBoot 她神秘的面纱

-

熟悉后端服务开发的小伙伴,在使用 SpringBoot 时一定会有这样的感受,咦,以前繁琐的配置,现在都不用再去配置一大堆东西了,以前跑起来一个 demo,感觉真是千辛万苦,错一步就 game over,以前服务基本都是已 war 包的形式运行在 Tomcat 中,而现在,你基本不需要手动写太多的代码,一个应用服务就可以运行起来,其次现在应用基本已 jar 包方式直接运行,虽然本质还是运行在 Tomcat 中,但现在 jar 包中已经有了服务运行的基础环境,可以直接使用 jar 相关的运行命令就可以运行起服务。好了,废话了这么多,先看看我们如何运行起一个 DEMO 应用。

-

环境及版本

-
    -
  • SpringBoot Version:2.1.6.RELEASE
  • -
  • System:macOS Mojave
  • -
  • JDK Version:1.8
  • -
  • Gradle:5.4.1
  • -
  • IDE:IntelliJ IDEA
  • -
-
-

本系列应用使用如上环境,其次应用包管理,小伙伴可以选择自己熟悉的 Maven 进行管理,而这里都使用 Gradle 进行管理

-
-

Demo

-

Spring Initializr

-

为了让开发者快速上手,官方提供了一建生成 SpringBoot 项目,你按需选择你需要的依赖即可。操作步骤如下截图
-spring-initializ

-

IDEA Init

-

-

IDEA分为四步完成初始

-
    -
  1. 选择 Spring Initializr 初始化向导
  2. -
  3. 填写项目坐标信息,构建工具,版本,报名等
  4. -
  5. 选择需要的组件(会自动添加依赖)
  6. -
  7. 选择项目存放路径
  8. -
-

Spring 运行

-

命令

-

macOS or Linux

-
# 项目路径下(spring-start)
gradlew bootRun
-

Windows

-
# 项目路径下(spring-start)
./gradlew bootRun
-

运行说明

-

spring-running-logo

-

Spring 打包

-

jar 分析

-

springboot-deploy-jar-unzip

-

目录说明

-
project/
├── BOOT-INF/
│ ├── classes # 当前项目结果文件放置在 classes 路径下
│ │ │ └── application.properties # 项目中配置文件
│ │ ├── org/ # 项目中 java 路径下,编译成 class 文件路径
│ │ ├── static/ # 项目中 resources 路径下的静态文件夹
│ │ └── templates/ # 项目中 resources 路径下的模板文件夹
│ └── lib/ # 项目所依赖的第三方 jar(Tomcat,SpringBoot 等)
├── META-INF/
│ └── MANIFEST.MF # 清单文件,用于描述可执行 jar 的一些基本信息
└── org/springframework/boot/loader/ # jar 包启动相关的引导
├── archive/
├── data
├── ExectableArchiveLauncher.class
├── jar/
├── JarLauncher.class
├── LaunchedURLClassLoader.class
├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
├── Launcher.class
├── MainMethodRunner.class
├── PropertiesLauncher.class
├── PropertiesLauncher$1.class
├── PropertiesLauncher$ArchiveEntryFilter.class
├── PropertiesLauncher$PrefixMatchingArchiveFilter.class
├── PropertiesLauncher$ArchiveEntryFilter.class
├── util/
└── WarLauncher.class
-

MANIFEST.MF

-
Manifest-Version: 1.0                                       # 清单版本号
Start-Class: org.incoder.start.SpringbootStartApplication # 项目 main 方法所在的类
Spring-Boot-Classes: BOOT-INF/classes/ # 项目相关代码在打包后 jar 中的路径
Spring-Boot-Lib: BOOT-INF/lib/ # 项目中所依赖的第三方 jar 在打包后 jar 中的路径
Spring-Boot-Version: 2.1.6.RELEASE # 项目 SpringBoot 版本
Main-Class: org.springframework.boot.loader.JarLauncher # 当前 jar 文件的执行入口类(main 方法所在的类)
回车换行(在清单文件中,必须有,否则会出错)
-

org/springframework/……目录

-

项目中引入的第三方 jar 中并不包含org/springframework/boot/loader内容,那这个目录是从哪里来的呢?

-

寻找最终发现是项目中我们的build.gradle文件中,引入的org.springframework.boot:spring-boot-gradle-plugin依赖,而这个依赖位于classpath下,说明引入的这个插件 仅仅 是在项目构建时才起作用,当项目进行打包后,并不会把插件包打入到项目的依赖库中,也就是BOOT-INF/lib/路径下

-

如何去研究在org/springframework/boot/loader下的源码内容呢?
-最好的方式是在项目的依赖中导入org.springframework.boot:spring-boot-loader依赖

-
-

原则上,在项目开发过程中是不需要引入org.springframework.boot:spring-boot-loader依赖,这里只是为了方便阅读源码进行学习

-
-

Spring 其他

-

配置文件格式

-
    -
  • properties
  • -
  • 推荐 yml
  • -
-
-

配置文件学习可参考 SpringBoot(四)配置文件

-
-

常用命令

-

gradle tasks

-

表示获取当前工程可用的 gradle tasks 命令

-
Application tasks
-
    -
  • bootRun:Runs this project as a Spring Boot application.(以 bootJar 的形式运行当前项目)
  • -
-
Build tasks
-
    -
  • bootJar:Assembles an executable jar archive containing the main classes and their dependencies.(装配一个可执行的 jar(自包含的 jar 包,不依赖其他容器) 归档,这个归档 jar 中包含了所需的依赖以及主类等)
  • -
-
Run jar
-
java -jar jar-name.jar
-
Other
-
# 解压 jar 到当前 start 目录下
unzip start-0.0.1-SNAPSHOT.jar -d ./start
]]>
- - SpringBoot - - - SpringBoot - -
- - SpringBoot(十)Mybatis 常用标签 - /2020/03/24/springboot10/ -

- -

关于 Mybatis 作为在国内普遍使用的 ORM 框架,我们在使用上要掌握常用的标签

-

mybatis-label

-

附录

-
    -
  • 官方教程《XML 映射器》
  • -
  • 官方教程《动态 SQL》
  • -
  • Mybatis常用标签
  • -
-]]>
- - SpringBoot - - - SpringBoot - Mybatis - -
- - SpringBoot 源码构建 - /2020/12/31/springboot11/ - 前两天刚刚学习了 Gradle 构建 SpringBoot 项目,再查看官方文档时,得知 SpringBoot 从 Spring Boot 2.3.0.M1 版本开始完全切换到使用 Gradle 来构建项目,那么本篇文章就来实践,基于源码来编译构建 SpringBoot,话不多说,本次构建构建是 2020 年的最后一次发布的版本 2.4.1

- -

环境

-
    -
  • OS:macOS 11.1
  • -
  • JDK:JDK1.8
  • -
  • Gradle:6.7.1-bin
  • -
  • IDE:IntelliJ IDEA Community 2020.3
  • -
-

Gradle 版本通过 https://github.com/spring-projects/spring-boot/blob/master/gradle/wrapper/gradle-wrapper.properties 文件可知,使用的 6.7.1-bin,那么本地也使用该版本编译,对于 Gradle 的安装可参考 Gradle(一)基础 文章

-

获取源码

-
# 这里使用 cnpmjs 来提高 clone 速度
git clone https://github.com.cnpmjs.org/spring-projects/spring-boot.git
-

编译构建

-

使用 IDEA 打开项目,会自动创建索引以及,下载项目的依赖,由于依赖的 jar 比较多,建议使用 阿里云 镜像,关于 Gradle 怎么修改依赖镜像源,可参考 专治各种网络不服 文章,阿里云镜像能加速大部分的 jar,但有一部分在阿里云上并没有,你可以通过手动方式导入到本地

-]]>
- - SpringBoot - - - SpringBoot - Gradle - -
- - SpringBoot(二) 启动分析JarLauncher - /2019/07/05/springboot2/ - 我们在开发过程中,使用 java -jar you-jar-name.jar 命令来启动应用,它是如何启动?以及它如何去寻找 .class 文件并执行这些文件?本节就带着这两个问题,让我们一层层解开 SpringBoot 项目的 jar 启动过程,废话不多说,跟着我的脚步一起去探索 spring-boot-load 的秘密。

-

SpringBoot(一)初识 已经解释了为什么在编译后的 jar 中根目录存在 org/springframework/boot/loader 内容,以及为了方便学习研究,我们需要在项目的依赖中导入 org.springframework.boot:spring-boot-loader 依赖。同时我们在解压的 you-jar-name.jar 文件中,查看对应的清单文件 MANIFEST.MF 内容,其中明确指出了应用的入口 org.springframework.boot.loader.JarLauncher 因此我们就从 JarLauncher 开始一步步深入

- -

spring-boot-loader-jarlauncher

-

结构

-

先用Diagrams来表述 JarLauncher 类之间的结构及方法等相关信息
-jarlauncher

-

从Diagrams可知

-
    -
  • 继承关系:JarLauncher extends ExecutableArchiveLauncher extends Launcher
  • -
  • 启动入口:JarLauncher main 方法
  • -
-
-

关于图上图标含义,这里就不再赘述,烦请移步 IntelliJ IDEA Icon reference

-
-

流程分析

-

jar规范

-

对于 Java 标准的 jar 文件来说,规定在一个 jar 文件中,我们必须要将指定 main.class 的类直接放置在文件的顶层目录中(也就是说,它不予许被嵌套),否则将无法加载,对于 BOOT-INF/class/ 路径下的 class 因为不在顶层目录,因此也是无法直接进行加载, 而对于 BOOT-INF/lib/ 路径的 jar 属于嵌套的(Fatjar),也是不能直接加载,因此 Spring 要想启动加载,就需要自定义实现自己的类加载器去加载。

-
-

关于 jar 官方标准说明请移步

-
    -
  • JAR File Specification
  • -
  • JAR (file format)
  • -
-
-

源码分析

-

main 方法

-

根据清单文件 MANIFEST.MFMain-Class 的描述,我们知道入口类就是 JarLauncher;先看下这个类的 javadoc 介绍

-
/**
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
* included inside a {@code /BOOT-INF/lib} directory and that application classes are
* included inside a {@code /BOOT-INF/classes} directory.
*
* 用于基于JAR的归档。这个启动程序假设依赖jar包含在{@code /BOOT-INF/lib}目录中,
* 应用程序类包含在{@code /BOOT-INF/classes}目录中
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
-

紧接着,要进行源码分析,那肯定是找到入口,一步步深入,那么对于 JarLauncher 就是它的 main 方法了

-
public static void main(String[] args) throws Exception {
// launch 方法是调用父类 Launcher 的 launch 方法
new JarLauncher().launch(args);
}
-

那我们去看一看 Launcher 的 launch 方法

-
/**
* Launch the application. This method is the initial entry point that should be
* called by a subclass {@code public static void main(String[] args)} method.
*
* 启动一个应用,这个方法应该被初始的入口点,这个入口点应该是一个Launcher的子类的
* public static void main(String[] args)这样的方法调用
*
* @param args the incoming arguments
* @throws Exception if the application fails to launch
*/
protected void launch(String[] args) throws Exception {
// 1. 注册一些 URL的属性
JarFile.registerUrlProtocolHandler();
// 2. 创建类加载器(LaunchedURLClassLoader),加载得到集合要么是BOOT-INF/classes/
// 或者BOOT-INF/lib/的目录或者是他们下边的class文件或者jar依赖文件
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// 3. 启动给定归档文件和完全配置的类加载器的应用程序
launch(args, getMainClass(), classLoader);
}
-

getClassPathArchives 方法

-launch 方法的第一步的相关内容比较简单,这里不做过多说明,主要后面两步,我们先看第二步,创建一个类加载器(ClassLoader),其中 getClassPathArchives() 方法是一个抽象方法,具体的实现有(ExecutableArchiveLauncherPropertiesLauncher ,因为我们研究的 JarLauncher 是继承 ExecutableArchiveLauncher ,因此我们这里看 ExecutableArchiveLauncher 类中 getClassPathArchives() 方法的实现)我们要看看这个方法中它做了什么 -
@Override
protected List<Archive> getClassPathArchives() throws Exception {
// 得到一个Archive的集合(BOOT-INF/classes/)和(BOOT-INF/lib/)目录所有的文件
// a. this.archive 中当前类的 archive 是怎么来的?
// b. getNestedArachives()是如何获得一个嵌套的 jar 归档?
// c. this::isNestedArchive 这个方法引用它做了什么?
List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
// 一个事后处理的方法
postProcessClassPathArchives(archives);
return archives;
}
-this.archive 位于当前类 ExecutableArchiveLauncher 的构造方法中 -
public ExecutableArchiveLauncher() {
try {
// 调用 createArchive() 方法得到Archive
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

/////////////////////////////////////////////////////////////
// 紧接着我们查看 createArchive() 方法都做了什么 //
/////////////////////////////////////////////////////////////

// Launcher.class 中的 createArchive()方法
// 得到我们运行文件的Archive相关的信息
protected final Archive createArchive() throws Exception {
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
String path = (location != null) ? location.getSchemeSpecificPart() : null;
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
// 返回我们要执行的jar文件的绝对路径(java -jar xxx.jar中 xxx.jar的绝对路径)
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException("Unable to determine code source archive from " + root);
}
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
}
-

对于 getNestedArachives() 方法,它是 Archive 的接口

-
/**
* Returns nested {@link Archive}s for entries that match the specified filter.
*
* 返回与过滤器相匹配的嵌套归档文件
*
* @param filter the filter used to limit entries
* @return nested archives
* @throws IOException if nested archives cannot be read
*/
List<Archive> getNestedArchives(EntryFilter filter) throws IOException;

/////////////////////////////////////////////////////////////
// 紧接着我们查看 getNestedArchives() 的实现 //
/////////////////////////////////////////////////////////////

// 这里的参数 EntryFilter类型中有一个 matches(Entry entry) 方法,
// 这也是this::isNestedArchive所对应的实际方法
@Override
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
List<Archive> nestedArchives = new ArrayList<>();
for (Entry entry : this) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
}
-

this::isNestedArchive 方法引用,我们查看 isNestedArchive 抽象方法

-
/**
* Determine if the specified {@link JarEntry} is a nested item that should be added
* to the classpath. The method is called once for each entry.
*
* 确定指定的{@link JarEntry}是否是应该添加到类路径的嵌套项。对每个条目调用该方法一次
*
* @param entry the jar entry
* @return {@code true} if the entry is a nested item (jar or folder)
*/
protected abstract boolean isNestedArchive(Archive.Entry entry);

/////////////////////////////////////////////////////////////
// 紧接着我们查看 isNestedArchive() 实现 //
/////////////////////////////////////////////////////////////

// JarLauncher.class 中的 isNestedArchive()方法
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
// 如果是目录判断是不是BOOT-INF/classes/目录
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
// 如果是文件判断文件的前缀是不是BOOT-INF/lib/开头
return entry.getName().startsWith(BOOT_INF_LIB);
}
-

createClassLoader 方法

-

把符合条件的 Archives 作为参数传入到 createClassLoader() 方法,创建一个类加载器,我们跟进去,查看 createClassLoader() 方法

-
/**
* Create a classloader for the specified archives.
*
* 创建一个所指定归档文件的类加载器
*
* @param archives the archives
* @return the classloader
* @throws Exception if the classloader cannot be created
*/
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(archives.size());
// 遍历传进来的 archives,将每一个 Archive 的 URL(归档文件在磁盘上的完整路径)添加到 urls 集合中
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
//
return createClassLoader(urls.toArray(new URL[0]));
}


/**
* Create a classloader for the specified URLs.
*
* 创建指定 URL 的类加载器
*
* @param urls the URLs
* @return the classloader
* @throws Exception if the classloader cannot be created
*/
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
// 这里的 LaunchedURLClassLoader 是 SpringBoot loader 给我们提供的一个全新的类加载器
// 参数 urls 是 class 文件或者资源配置文件的路径地址
// 参数 getClass().getClassLoader() 是应用类加载器
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}


/**
* Create a new {@link LaunchedURLClassLoader} instance.
* @param urls the URLs from which to load classes and resources
* @param parent the parent class loader for delegation
*/
public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
-

super() 方法是调用父类的方法,这样一层层跟进去,最终到了 JDK 的 ClassLoader 类,它也是所有类加载器的顶类

-

launch 方法

-launch 方法的第二个参数,getMainClass() 是一个抽象方法 -
/**
* Returns the main class that should be launched.
* @return the name of the main class
* @throws Exception if the main class cannot be obtained
*/
protected abstract String getMainClass() throws Exception;

/////////////////////////////////////////////////////////////
// 紧接着我们查看 getMainClass() 实现 //
/////////////////////////////////////////////////////////////

@Override
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
// 获取到 Manifest 文件中属性为`Start-Class`对应的值,也就是当前项目工程启动的类的完整路径
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
-

接着我们看 launch 方法

-
/**
* Launch the application given the archive file and a fully configured classloader.
*
* 加载指定存档文件和完全配置的类加载器的应用程序
*
* @param args the incoming arguments
* @param mainClass the main class to run
* @param classLoader the classloader
* @throws Exception if the launch fails
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
// 将应用的加载器换成了自定义的 LaunchedURLClassLoader 加载器,然后入到线程类加载器中
// 最终在未来的某个地方,通过线程的上下文中取出类加载进行加载
Thread.currentThread().setContextClassLoader(classLoader);
// 创建一个主方法运行器运行
createMainMethodRunner(mainClass, args, classLoader).run();
}

/**
* Create the {@code MainMethodRunner} used to launch the application.
*
* 创建一个 MainMethodRunner 用于启动这个应用
*
* @param mainClass the main class
* @param args the incoming arguments
* @param classLoader the classloader
* @return the main method runner
*/
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
-

返回一个 MainMethodRunner 对象,我们紧接着去看看这个对象,

-
/**
* Utility class that is used by {@link Launcher}s to call a main method. The class
* containing the main method is loaded using the thread context class loader.
*
* 被 Launcher 使用来调用 main 方法的辅助类,使用线程类加载来加载包含 main 方法的类
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class MainMethodRunner {

private final String mainClassName;

private final String[] args;

/**
* Create a new {@link MainMethodRunner} instance.
* @param mainClass the main class
* @param args incoming arguments
*/
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}

// 关键方法
public void run() throws Exception {
// 获取到当前线程上下文的类加载器,实际就是 springboot 自定义的加载器(LaunchedURLClassLoader)
// 加载 this.mainClassName所对应的类,实际也就是清单文件中对应 Start-Class 属性的类
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
// 通过反射获取到 main 方法和参数
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
// 调用目标方法运行
// invoke 方法参数一:是被调用方法所在对象,这里为 null,原因是我们所调用的目标方法是一个静态方法
// invoke 方法参数二:被调用方法所接收的参数
mainMethod.invoke(null, new Object[] { this.args });
}

}
-

到此为止,invoke 方法成功调用,那么我们项目中的main 方法就执行了,这时我们的所编写的 springboot 应用就正式的启动了。那么关于 springboot 的 loader 加载过程已经分析完

-

总结

-

summary-jarlauncher

-

从 jar 规范的角度出发,我们深入分析了 springboot 项目启动的整个过程,这个过程到底对不对,我们口说无凭,需要实际检验我们分析
-首先,我们先思考,项目的应用启动入口是不是必须是 main.class 方法,以及为什么要默认这么做?
-其次,我们再思考,在编辑器中通过图标运行启动程序(或者是通过命令启动程序),比较将程序编译成 jar 包,然后通过命令启动程序他们之间是否相同,如果不同请解释为什么?

-

问题一

-

项目的应用启动入口可以不是 main.class 方法,只是为什么会默认为 main.class 方法,原因是在 springboot 的 MainMethodRunner类的 run 方法中,是固定写死的 main ,为什么要这么写,答案是,我们可以在编辑器中已右键或其他图标启动的方式快速启动 springboot 项目(就像是在运行一个 Java 的 main 方法一样,不再向之前需要乱七八糟各种的配置)。

-

问题二

-

答案是不相同,我们可以在项目的应用启动 main.class 方法中,打印出加载类 System.out.println(项目启动加载类 + SpringbootStartApplication.class.getClassLoader()); ,这样就可以检验我们的分析是否正确。分别使用两种不同的方式

-
    -
  • 方式一:在编辑器中之间运行(右键,或者控制台输入命令gradle bootRun)或者使用 IDEA 上的运行应用运行按钮,结果如下
    项目启动加载类sun.misc.Launcher$AppClassLoader@18b4aac2
    -
  • -
  • 方式二:先编译成 jar 包,然后通过 java -jar build-name.jar 命令运行
    项目启动加载类org.springframework.boot.loader.LaunchedURLClassLoader@439f5b3d
    -
  • -
-

通过打印出来的信息,可以验证我们的分析,方式一的运行,实际上是应用类加载器启动,而方式二是 spring-boot-load 包中自定义的 LaunchedURLClassLoader 来启动项目

-

在实际的生产开发中,有时我们的分析需要进行验证(或者找问题),而此时服务又部署在生成环境或者非本机上,通常用的方式是看应用的日志输出,在日志中去定位问题,而有时我们需要断点的方式去找问题,那该如何去操作呢?对于这个问题,在实际开发中是有方法去处理,请看下篇《SpringBoot(三) JDWP远程调用》

-

附录

-
    -
  • spring_boot_cloud(2)Spring_Boot打包文件结构深入分析源码讲解
  • -
  • 校验者•CeaserWang
  • -
-]]>
- - SpringBoot - - - SpringBoot - -
- - SpringBoot(三) JDWP远程调用 - /2019/07/11/springboot3/ - 在 SpringBoot 系列的第二篇文章中,已经详细分析了 SpringBoot 的启动过程,那么这篇文章,我们通过源码调试的方式来验证我们的分析,首先我们在控制台中输入 java 命令,可用输出 JDK 给我们提供了一些命令,其中-agentlib命令就是本篇文章所介绍,用于我们进行源码调试

- -

springboot-java-agentlib
-我们继续查看-agentlib详细的命令说明,输入java -agentlib:jdwp=help 查看帮助文档
-springboot-java-agentlib-help

-

远程

-
# 在远程机器上添加代理模式的方式启动
# 使用 socket 协议来进行远程调试,当服务启动就开始在 6666 端口等待连接
java -agentlib:jdwp=transport=dt_socket,server=y,address=6666 -jar start-1.0-SNAPSHOT.jar
-

本机

-

在本机上,我们直接使用 IDEA 编辑器,新建一个 Remote 应用服务,运行,创建步骤如下 9 步骤
-springboot-java-remote

-]]>
- - SpringBoot - - - SpringBoot - -
- - SpringBoot(四)配置文件 - /2019/07/28/springboot4/ - 关于 SpringBoot 配置文件,在之前的文章中已经提到配置文件格式,主要是两种格式的配置,这里并没有哪个配置写法一定优于另一种写法,对于配置文件名(application.yml 或者 application.properties),可以更改,为了减少不必要的麻烦,不建议修改,本篇文章以 yml 文件作为示例

- -

本篇文章示例代码见:springboot-config

-

YAML

-

YAML是JSON的超集,因此是用于指定分层配置数据的便捷格式。只要在类路径上有SnakeYAML库,SpringApplication类就会自动支持YAML作为属性的替代 。

-

语法规则

-
    -
  • 大小写敏感
  • -
  • 缩进(只能使用空格,空格数量不重要)表示层级
  • -
  • 注释用 # 符号
  • -
-

数据结构

-
    -
  1. 不可再分的单个的值,如数字,字符串等。
    env: dev
    crate-date: 2020
    is-mac: true

    # ~表示NULL值
    email: ~

    # 多行字符串可以使用 | 保留换行符,也可以使用 > 折叠换行
    # + 表示保留文字块末尾的换行,- 表示删除字符串末尾的换行
    message: |-
    hello world ${crate-date}

    # 单引号
    # 会转义特殊字符,特殊字符最终只是一个普通的字符串数据
    # 输出:mac \n catalina
    name1: 'mac \n catalina'

    # 双引号
    # 不会转义字符串里面的特殊字符,特殊字符会作为本身想表示的意思
    # 输出:mac
    # catalina
    name2: "mac \n catalina"
    -
  2. -
  3. 数组,一组按次序排列的值
    # 这种写法,必须有两层结构,而且第二层(language 名字)是必须满足 Java 属性字段命名规则
    list:
    language:
    - 'object-c'
    - 'swift'
    - 'c'
    # 或者行内写法
    list-program-languages: object-c, swift, c
    # SpEL 获取数组
    el:
    list: object-c, swift, c
    -
  4. -
  5. 对象,键值对的集合
    # 对象
    object:
    name: Jerry
    age: 20

    # 或者行内写法
    persons: { name: Jerry, age: 20 }

    # Map
    map-object:
    map:
    key1: value1
    key2: value2

    # 或者行内写法
    mapObjects.maps: { key1: value1,key2: value2 }
    -
  6. -
  7. 随机数
    # 随机数
    secret: ${random.value}
    number: ${random.int}
    bignumber: ${random.long}
    uuid: ${random.uuid}
    -
  8. -
  9. 默认值,占位符获取之前配置的值,如果没有可以是用:指定默认值
    bootapp.name: SpringBoot
    description: ${bootapp.name}是一个spring应用程序
    -
  10. -
-
    -
  1. : 号后面有一个空格
  2. -
  3. 对于复杂的数据结构(对象,List,Map),需要配套 @ConfigurationProperties 定义对应的对象
  4. -
-
-

配置

-

为了在配置自定义属性时,向配置 SpringBoot 属性自动提示的功能,导入如下的包

-
<!--导入配置文件处理器,配置文件进行绑定就会有提示-->
<!-- @ConfigurationProperties annotation processing (metadata for IDEs)
生成spring-configuration-metadata.json类,需要引入此类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
-

或者在 gradle 配置文件中添加依赖

-
compileOnly 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
-

如果任然无法自动提示,请查看你的编辑器 IDEA 中是否开启了 Annonation Processing
-idea-annotation-processors

-

定义配置

-

默认配置

-

Common application properties,是 SpringBoot 官方提供的一些默认配置属性说明,如果需要更改,只需要在 application.yml 文件中重写该属性即可,比如重新设置服务的启动端口为 9090

-
server:
port: 9090
-

自定义配置

-

首先在 application.yml 文件中根据上面所述的规则写法进行自定义字段的声明

-
myConfig:
maps:
key: value
-

配置的使用

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@Value@ConfigurationProperties@PropertySource
使用场景单一属性注入,注解写在类的属性上批量注入,注解写在类上加载自定义配置文件,用于静态类获取配置文件中定义的信息
松散语法不支持支持支持
SpEL支持不支持支持
JSR-303 数据校验 @Validated不支持支持支持
复杂类型(数组,Map,对象等)不完全支持(数组支持行内定义)支持支持
-

ConfigurationProperties

-

@ConfigurationProperties 注解是 SpringBoot提供的一种使用属性的注入方法,不仅可以方便的把配置文件中属性值与所注解类绑定,还支持松散绑定,JSR-303 数据校验等功能

-
/**
* 使用 @ConfigurationProperties 配置属性必须是小写,多单词之间可用'-'连接
*
* @author : Jerry xu
* @since : 2020/3/29 16:26
*/
@Data
@Component
@ConfigurationProperties(prefix = "list")
public class ConfigListBean {

/**
* 这里不能再用 @Value("") 去加载,修饰当前的字段,
* 必须指定其属性名和配置文件中定义的名称一致
*/
private List<String> language;

}
-

Value

-

@Value 注解支持直接从配置文件中读取值,同时支持 SpEL 表达式,但是不支持复杂数据类型和数据验证

-

使用 @Value 获取配置文件中定义的值,通常其类是被 @Controller@Service@Component 等注解修饰,如果是一般普通的类(如一些工具类)并 不能 只接获取到配置文件中定义的值

-
@Data
@Component
public class GradleDataBean {

@Value("${env}")
private String env;
@Value("${crate-date}")
private Integer createDate;
@Value("${is-mac}")
private Boolean isMac;
@Value("${email}")
private String email;
@Value("${message}")
private String message;
@Value("${name1}")
private String name1;
@Value("${name2}")
private String name2;

@Value("${list-program-languages}")
private List<String> programLanguages;

/**
* 使用el表达式,获取定义数组
*/
@Value("#{'${el.list}'.split(',')}")
private List<String> programList;

@Value("${secret}")
private String secret;
@Value("${number}")
private Integer number;
@Value("${bignumber}")
private Long bigNumber;
@Value("${uuid}")
private String uuid;
}
-

PropertySource

-

@PropertySource 注解加载自定义配置文件,由于 @PropertySource 指定的文件会优先加载,所以如果在 applocation.properties 文件中存在相同的属性配置,会覆盖前者中对应的值,且 @PropertySource 不支持 yml 文件注入

-

多环境配置

-

在实际的开发中,不同环境对应不同的配置,因此我们需要根据环境来配置不同的项目配置信息,SpringBoot 也是支持我们进行多环境的配置,通常情况下,我们命名为 application-{profile}.properties/yml ,其中
-{profile}表示不同的环境,比如:dev(开发),prod(线上) 等

-

关于具体的多环境配置,可以参考文章 SpringBoot(五)多环境配置

-

配置文件加载顺序

-

Spring Boot 启动会扫描以下位置的配置文件(application.properties 或 application.yml) 作为Spring Boot 的默认配置文件

-
    -
  1. -file:./config/
  2. -
  3. -file:./
  4. -
  5. -classpath:/config/
  6. -
  7. -classpath:/
  8. -
-

加载顺序可以查看下图
-application-sort

-

优先级从高到低,高优先级的配置会覆盖低优先级的配置

-

参考

-
    -
  • 介绍两种SpringBoot读取yml文件中配置数组的方法
  • -
  • Spring的@Value可以注入复杂类型吗?今天教你通过@value注入自定义类型
  • -
  • Properties and Configuration
  • -
-]]>
- - SpringBoot - - - SpringBoot - -
- - SpringBoot(五)多环境配置 - /2020/02/02/springboot5/ - 在 SpringBoot 项目中,常用的包管理分别为 maven 和 gradle,在不同包管理下我们如何实现多环境的项目配置,这是实际项目开发过程汇总必备的一项技能,可以大大提高我们开发部署效率,同时也避免了人为的频繁改动配置造成的问题等,有些人可能会问了,maven 不是用的好好的嘛,干嘛还要用 gradle,首先我们可以看现在主流开源项目在提供引入方式时都是有提供了 gradle 依赖方式,以及 gradle 支持编写脚本,在很大程度上让管理更加便捷和人性化

- -

废话不多说,我们直接来看代码吧,首先我们先来看看使用 maven 来进行多环境的配置

-

maven

-

pom.xml 文件

-
<!-- pom.xml <project>标签下配置环境变量名 -->
<profiles>
<profile>
<!-- 本地开发环境 -->
<id>dev</id>
<properties>
<profileActive>dev</profileActive>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<!-- 正式环境 -->
<id>prod</id>
<properties>
<profileActive>prod</profileActive>
</properties>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
</profile>
</profiles>

<!-- pom.xml <project>标签下配置打包根据环境导入的文件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<!-- 文件加载配置-->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>application*.yml</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>application.yml</include>
<include>application-${profileActive}.yml</include>
</includes>
</resource>
</resources>
</build>
-

application*.yml 文件

-

根据上面我们的配置,我们首先排除 resources 目录下的所有 application*.yml 文件,然后再导入 application.ymlapplication-${profileActive}.yml 文件,那么通常在 application.yml 文件中我们放置一些与环境无关的配置,在 application-${profileActive}.yml 文件中根据不同环境配置不同的属性(比如:数据库连接,日志等级等配置)

-

根据 SpringBoot 加载资源文件的顺序,如果我们在 application-${profileActive}.yml 文件中配置了与 application*.yml 文件相同的属性,那么 application-${profileActive}.yml 会覆盖掉 application*.yml 文件中相同属性配置

-

application.yml

-
# 动态激活运行的环境,默认是 dev
# 当然你也可以在你的 pom.xml 文件中进行默认激活的环境更改
spring:
profiles:
active: @profileActive@
-

gradle

-

编译打包

-

部署

-

关于应用的部署,可以参考IDEA 之 SpringBoot 应用部署,这里不再过多进行说明

-

附录

-
    -
  • SpringBoot 2.1.6.RELEASE 官方指南
  • -
  • SpringBoot 中文指南
  • -
  • Gradle Builds From Apache Maven
  • -
  • 灵活强大的构建系统 Gradle
  • -
-]]>
- - SpringBoot - - - SpringBoot - -
- - SpringBoot(六)日志管理 - /2020/02/22/springboot6/ - 日志是我们项目开发过程中必不可少的一个方面, 当我们项目引入了 spring-boot-starter-web 这个 jar 包,会自动引入相关的一些日志相关的 jar 包,比如,其实在项目中可供我们选择的 jar 包有很多,比如 log4jlog4j2logback(现在使用最多),slfj等,在具体使用时并不会直接去使用log4jlog4j2而是使用slfj作为门面,具体的实现是通过可插拔的方式提供。logback实际是在log4j之后作者重新写的一个日志框架。本篇文章主要讲logback在项目中的应用

- -

logback-spring.xml

-

文件名使用 logback-spring.xml,位于 resource 路径下

-]]>
- - SpringBoot - - - SpringBoot - -
- - SpringBoot(七)注解 - /2020/02/25/springboot7/ - SpringBoot 与 SpringCloud 微服务技术栈体系本质就是围绕注解来展开,这些注解在微服务框架中扮演非常重要的角色,每个注解都有他的应用场景,通过一些注解的组合让 SpringBoot 与 SpringCloud 开发变的简单和高效,本篇文章我们就来汇总 SpringBoot 相关的注解

-

本篇文章基于如下版本

-
    -
  • Spring:5.1.8 RELEASE
  • -
  • SpringBoot:2.1.6 RELEASE
  • -
- -
-

由于本篇包含众多注解,请配合 Ctrl + F (或 + F)使用
-绿色基础 的注解
-红色常用 的注解

-
-

由于 SpringBoot 的基础是 Spring,SpringBoot 相关部分注解都是在 Spring 的基础注解上的再组合,因此我们先来学习 Spring 的相关基础注解,在注解中大部分注解的修饰中包含已下几个注解,这里统一来说明下

-

Java 相关

-

@Documented

-

在默认情况下Documented注解表明这个注释是由 javadoc 记录的也有类似的记录工具。 如果一个类型声明被注释了文档化,它的注释成为公共API的一部分

-
    -
  • 路径:java.lang.annotation
  • -
  • 引入:JDK 1.5 开始引入
  • -
-

@Inherited

-

说明子类可以继承父类中的该注解,注释类型是自动继承的。

-
    -
  • 如果注释类型声明中存在继承的元注释,并且用户在类声明中查询该注释类型,并且该类声明中没有该类型的注释,则将自动查询该类的超类以获取注释类型。重复此过程,直到找到该类型的注释,或到达类层次结构(对象)的顶部为止。
  • -
  • 如果没有超类对此类型进行注释,则查询将指示所讨论的类没有此类注释。
  • -
-
-

请注意,如果带注释的类型用于 注释除类之外 的任何内容,则此元注释类型无效。还要注意,此元注释仅使注释从超类继承;已实现的接口上的注释无效

-
-
    -
  • 路径:java.lang.annotation
  • -
  • 引入:JDK 1.5 开始引入
  • -
-

@Retention

-

注解的保留位置,RetentionPolicy 是提供的策略枚举

-
    -
  1. SOURCE:注解将被编译器丢弃,比如:@Override,@SupressWarnings
  2. -
  3. CLASS:注释将由编译器记录在类文件中,但不必在运行时由VM保留。这是默认的行为
  4. -
  5. RUNTIME:注释由编译器记录在类文件中,并由在运行时由VM保留,因此可以通过反射方式读取它们,比如 @Deprecated
  6. -
-
-

@Retention(RetentionPolicy.RUNTIME) // 作用于运行期

-
-
    -
  • 路径:java.lang.annotation
  • -
  • 引入:JDK 1.5 开始引入
  • -
-

@Target

-

用于描述注解的使用范围(即:被描述的注解可以用在什么地方),ElementType 是提供的策略枚举

-
    -
  1. ANNOTATION_TYPE:用于注释类型
  2. -
  3. CONSTRUCTOR:用于描述构造器
  4. -
  5. FIELD:用于描述字段(包括枚举常量)
  6. -
  7. LOCAL_VARIABLE:用于描述局部变量
  8. -
  9. METHOD:用于描述方法
  10. -
  11. PACKAGE:用于描述包
  12. -
  13. PARAMETER:用于描述参数
  14. -
  15. TYPE:用于描述类、接口(包括注解类型) 或enum声明
  16. -
  17. TYPE_PARAMETER:用于参数类型
  18. -
  19. TYPE_USE:用于使用类型
  20. -
-
-

@Target(ElementType.ANNOTATION_TYPE) // 作用于注释类型

-
-
    -
  • 路径:java.lang.annotation
  • -
  • 引入:JDK 1.5 开始引入
  • -
-

Spring 相关

-

context.annotation

-

路径:org.springframework.context.annotation

-

@Bean

-
    -
  • 引入:Spring 3.0 开始引入
  • -
-

@ComponentScan

-

一个组合注解,默认会装配标识了@Controller,@Service,@Repository,@Component 注解到 Spring 容器中

-
    -
  • 引入:Spring 3.1 开始引入
  • -
-
-

@ComponentScan 与 @ComponentScans

-
-

@Conditional

-

可以根据代码中设置的条件装载不同的 bean,在设置条件注解之前,先要把装载的 bean 类去实现 Condition 接口,然后对该实现接口的类设置是否装载的条件。

-

SpringBoot 注解中的 @ConditionalOnProperty,@ConditionalOnBean 等以 @Conditional* 开头的注解,都是通过集成了@Conditional 来实现相应功能

-
    -
  • 引入:Spring 4.0 开始引入
  • -
-

@Configuration

-

用于自定义配置类,可替换 XML 配置文件,被注解的类内部包含有一个或多个被 @Bean 注解的方法,这些方法会将被 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描,并用于构建 bean 定义,初始化 Spring 容器

-
    -
  • 引入:Spring 3.0 开始引入
  • -
-

@DependsOn

-

@Description

-

@EnableAspectJAutoProxy

-

@EnableLoadTimeWeaving

-

@EnableMBeanExport

-

@Import

-

通过导入的方式实现把实例加入 SpringIOC 容器中。可以在需要时将没有被 Spring 容器管理的类导入至 Spring 容器中

-
    -
  • 引入:Spring 3.0 开始引入
  • -
-

@ImportResource

-

和 @Import 类似,区别就是 @ImportResource 导入的是配置文件

-
    -
  • 引入:Spring 3.0 开始引入
  • -
-

@Lazy

-

@Primary

-

@Profile

-

@PropertySource

-
-

@PropertySource 与 @PropertySources

-
-

@Role

-

@Scope

-

stereotype

-

路径:org.springframework.stereotype

-

@Component

-

是一个元注解,意思是可以注解其他类注解,比如:@Controller @Service @Repository 带此注解的类被看做组件,当使用基于注解的配置和类路径扫描的时候,这些类就会被实例化。其他类级别的注解也可以被认定为是一种特殊类型的组件,比如@Controller(注入服务),@Service(注入 DAO),@Repository(实现 DAO 访问)。

-

@Component 泛指组件,当组件不好归类时,我们可以使用这个注解进行标注,作用相当于 XML 配置,<bean id="" class="" >

-
    -
  • 引入:Spring 2.5 开始引入
  • -
-

@Controller

-

@Indexed

-

@Repository

-

@Service

- -

SpringBoot 相关

-

spring-boot

-

spring-boot:2.1.6.RELEASE

-

@SpringBootConfiguration

-

路径:org.springframework.boot

-

context.properties

-

路径:org.springframework.boot.context.properties

-
@ConfigurationProperties
-
@ConfigurationPropertiesBinding
-
@DeprecatedConfigurationProperty
-
@EnableConfigurationProperties
-
@NestedConfigurationProperty
-

convert

-

路径:org.springframework.boot.convert

-
@DataSizeUnit
-
@Delimiter
-
@DurationFormat
-
@DurationUnit
-

jackson

-

路径:org.springframework.boot.jackson

-
@JsonComponent
-

web.server

-

路径:org.springframework.boot.web.server

-
@LocalServerPort
-

web.servlet

-

路径:org.springframework.boot.web.servlet

-
@ServletComponentScan
-

spring-boot-autoconfigure

-

spring-boot-autoconfigure:2.1.6.RELEASE

-

org.springframework.boot.autoconfigure

-

@AutoConfigurationPackage

-

@AutoConfigureAfter

-

@AutoConfigureBefore

-

@AutoConfigureOrder

-

@EnableAutoConfiguration

-

@ImportAutoConfiguration

-

@SpringBootApplication

-

表示一个配置类,它声明一个或多个 Bean 方法并且会触发自动配置以及组件扫描,这是一个很便捷的注解,@SpringBootApplication 相当于同时使用 @Configuration、 @EnableAutoConfiguration、 @ComponentScan这三个注解

-
@Target(ElementType.TYPE)             // 当前注解所修饰的对象范围:用于描述类,接口,enum声明
@Retention(RetentionPolicy.RUNTIME) // 作用于运行期
@Documented // 生成文档
@Inherited // 继承
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

}
-
    -
  • 引入:SpringBoot 1.2.0开始引入
  • -
-

@SpringBootConfiguration

-

condition

-

路径:org.springframework.boot.autoconfigure.condition

-
@ConditionalOnBean
-
@ConditionalOnClass
-
@ConditionalOnCloudPlatform
-
@ConditionalOnExpression
-
@ConditionalOnJava
-
@ConditionalOnJndi
-
@ConditionalOnMissingBean
-
@ConditionalOnMissingClass
-
@ConditionalOnNotWebApplication
-
@ConditionalOnProperty
-
@ConditionalOnResource
-
@ConditionalOnSingleCandidate
-
@ConditionalOnWebApplication
-

data

-

路径:org.springframework.boot.autoconfigure.data

-
@ConditionalOnRepositoryType
-

domain

-

路径:org.springframework.boot.autoconfigure.domain

-
@EntityScan
-

flyway

-

路径:org.springframework.boot.autoconfigure.flyway

-
@FlywayDataSource
-

liquibase

-

路径:org.springframework.boot.autoconfigure.liquibase

-
@LiquibaseDataSource
-

quartz

-

路径:org.springframework.boot.autoconfigure.quartz

-
@QuartzDataSource
-

SpringCloud 相关

-

JPA 注解

-

@Column

-

@Entity

-

@GeneratedValue

-

@Id

-

@JoinColumn

-

@JsonIgnore

-

@MappedSuperClass

-

@NoRepositoryBean

-

@OneToOne、@OneToMany、@ManyToOne

-

@SequenceGeneretor

-

@Transient

-

异常

-

@ControllerAdvice

-

@ExceptionHandler

-

其他注解

-

@Autowired

-

@Inject

-

@JsonBackReference

-

@PathVariable

-

@Qualifier

-

@RepositoryRestResourcepublic

-

@RequestMapping

-

@Resource

-

@ResponseBody

-

@RestController

-

@Value

-

附录

-
    -
  • Spring Boot 注解如何系统的学习
  • -
  • SpringBoot 系列(三)Spring Boot 自动配置
  • -
  • Spring boot 2.x注解Annotation大全
  • -
-]]>
- - SpringBoot - - - SpringBoot - -
- - SpringBoot(八)SpringApplication 源码分析 - /2020/02/26/springboot8/ - 正如我们看到的 SpringBoot 应用启动入口类,main() 方法中一行简单 SpringApplication.run(MyApplication.class, args); 就可以将 SpringBoot 应用给启动了。那么它肯定是在SpringApplication中做了大量的工作,才能将应用启动,因此本篇文章我们来一起看看这个核心的类

-

SpringApplication 类可以从 Java 的 main 方法中引导和启动 Spring 的应用,默认情况下它会按照如下的启动步骤

-
    -
  1. 创建一个恰当的 ApplicationContext 实例(取决于你的 classpath 路径)
  2. -
  3. 注册一个 CommandLinePropertySource 将命令行参数作为 Spring 的属性(换句话说,可以通过命令行来传递当前应用所需要的一些属性)
  4. -
  5. 刷新应用的 Context(内容上下文),并加载所有单例的 beans
  6. -
  7. 触发每个 CommandLineRunner beans
  8. -
- -

大多数情况下,我们在 main() 方法中直接使用静态的 run(Class, String[]) 方法启动加载应用,对于一些高级的配置,我们可以创建一个 SpringApplication 实例,在运行之前进行自定义的配置

-
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
// ... customize application settings here
application.run(args)
}
-

SpringApplication 可以读取不同来源的 bean 信息,通常推荐我们使用被 @Configuration 修饰的单例类来启动应用,然而你也可以在多种来源去设置你的 source

-
    -
  • 使用AnnotatedBeanDefinitionReader加载完全限定类名
  • -
  • 使用XmlBeanDefinitionReader读取本地 XML 资源,或者使用GroovyBeanDefinitionReader 读取 Groovy 脚本去加载
  • -
  • 使用ClassPathBeanDefinitionScanner扫描得到包的名字
  • -
-]]>
- - SpringBoot - - - SpringBoot - -
- - SpringBoot(九)普通类如何获取配置文件中的值 - /2020/06/26/springboot9/ - 在之前的 SpringBoot 学习中,我们知道,可以在项目的 application.yml 文件或 application.properties 文件中获取到一些我们项目中的一些静态值。但对于一些非 Spring 所管理的类(比如一些工具类)该如何获取到定义在配置文件中的值呢?而这些工具类中的配置值和对应项目运行的环境有关,我们如果固定写在代码内,每次打包时去更改,显然这种做法不够好,那么本篇文章就来实践一些非 Spring 类如何获取配置的值

- -

《SpringBoot(四)配置文件》 文章中提到,在使用 @Value 注解时,当时提到说 “使用 @Value 获取配置文件中定义的值,通常其类是被 @Controller@Service@Component 等注解修饰,如果是一般普通的类(如一些工具类)并 不能 只接获取到配置文件中定义的值”,显然我们直接按照之前的套路,在application.yml等配置文件中添加自定义的配置,使用 @Value 注解在我们的普通类中是无法获取到的,其实要解决这个问题很简单,那就是将当前的普通类变成一个被 Spring 所接管的类(普通类上使用 @Component 来修饰),这样我们就可以使用了

-

就这?你以为就结束了,那我还写个啥笔记,你在操作完发现你依然拿不到在配置文件中定义的静态值,接下来让我一步步来告诉你怎么来写,和为啥会这么做的原因

-

配置文件

-

参考

-
    -
  1. 工具类用单例模式还是静态方法
  2. -
  3. springboot 工具类加载配置对象
  4. -
  5. 静态方法(工具类)中调用Spring管理的Bean
  6. -
  7. spring实现静态注入
  8. -
  9. 工具类该用单例模式,还是用静态的方式
  10. -
  11. 单例模式工具类中Spring 注入为空 的一些小问题
  12. -
-]]>
- - SpringBoot - - - SpringBoot - -
- - SpringCloud(一)Security OAuth2 - /2020/07/11/springcloud1/ - 什么是 OAuth2 -

用于 REST/APIs 的代理授权框架(delegated authorization framework),基于令牌 Token 的授权,在无需暴露用户密码的情况下,使应用能获取对用户数据的有限访问权限,做到解耦认证和授权

-

OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如:头像,照片,视频等),而在这个过程中无线将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据

- -

什么是 Spring Security

-

Spring Security 是为基于 Spring 的应用程序提供声明书安全保护的安全性框架。Spring Security 提供了完整的安全性解决方案,它能够在 Web 请求级别和方法调用级别处理身份认证和授权。因为基于 Spring 框架,所以 Spring Security 充分利用了依赖注入(Dependency Injection,DI)和面向切面的技术

-

最初,Spring Security 被称为 Acegi Security。Acegi 是一个强大的安全框架,但是它存在一个严重的问题,那就是需要大量的 XML 配置。到了 2.0 版本,Acegi Security 更名为 Spring Security,2.0版本所带来的不仅仅是名字的变化。为了在 Spring 中配置安全性,Spring Security 引入一个全新的、与安全性相关的 XML 命名空间。这个新的命名空间联通注解和一些合理的默认设置,将典型的安全性配置从几百行 XML 减少到十几行。Spring Security 3.0 融入了 SpEL,这将进一步简化 路安全性配置

-

Spring Security 从两个角度来解决安全问题。

-
    -
  • 它使用 Servlet 规范中的 Filter 保护 Web 请求并限制 URL 级别的访问
  • -
  • 它还能够使用 Spring AOP 保护方法调用——借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法
  • -
-

Spring Security OAuth 现状

-

Spring Security OAuth 的模块已被废弃,后续功能已经迁移到 Spring Security 5.2.x 中,但不会再提供 Authorization Server 的功能。

-

为此,随着 Spring Security5.2 发布,Spring 官方强烈鼓励用户开始将其旧版 OAuth2 客户端和资源服务器应用迁移到 Spring Security5.2 中的新支持

-

具体的功能列表请移步查看

-

OAuth2 主要角色

-
    -
  1. 客户应用(Client Application):通常是一个 Web 或无线应用,它需要访问用户的受保护资源
  2. -
  3. 资源服务器(Resource Server):是一个 Web 站点或者 Web service API,用户的受保护数据保存于此
  4. -
  5. 授权服务器(Authorized Server):在客户应用成功认证并获得授权之后,向客户应用颁发访问令牌 Access Token
  6. -
  7. 资源拥有者(Resource Owner):资源的拥有人,想要分享某些资源给第三方应用
  8. -
  9. 客户凭证(Client Credentials):客户的 clientId 和密码用于认证客户
  10. -
  11. 令牌(Tokens):授权服务器在接收到客户请求后,颁发的访问令牌 -
      -
    • 授权码(Authorization Code Token):仅用于授权码授权类型,用于交换获取访问令牌和刷新令牌
    • -
    • 刷新令牌(Refresh Token):用于去授权服务器获取一个新的访问令牌
    • -
    • 访问令牌(Access Token):用于代表一个用户或服务直接去访问受保护的资源
    • -
    • Bearer Token:不管谁拿到 Token 都可以访问资源,像现钞
    • -
    • Proof of Possession(PoP) Token:可以校验 client 是否对 Token 有明确的拥有权
    • -
    -
  12. -
  13. 作用域(Scopes):客户请求访问令牌时,由资源拥有者额外指定的细分权限
  14. -
-

OAuth2 误解

-
    -
  • OAuth 并没有支持 HTTP 以外的协议
  • -
  • OAuth 并不是一个 认证协议
  • -
  • OAuth 并没有定义 授权处理机制
  • -
  • OAuth 并没有定义 Token 格式
  • -
  • OAuth 2.0 并没有定义 加密方法
  • -
  • OAuth 2.0 并不是 单个 协议
  • -
  • OAuth 2.0 仅是 授权框架,仅用于授权代理
  • -
-

OAuth2 运行流程

-

来自 RFC 6749

-

从上面的流转过程,经过下面六个步骤,客户端就能获取到访问资源的令牌,其中第二步是关键,用户要怎样才能给予客户端授权(客户端获取授权的四种模式)

-
    -
  1. (A)用户打开客户端后,客户端要求用户给予权限
  2. -
  3. (B)用户同意给予客户端权限
  4. -
  5. (C)客户端使用上一步获取的授权,向认证服务器申请令牌
  6. -
  7. (D)认证服务器对客户端进行认证后,确认无误,同意发放令牌
  8. -
  9. (E)客户端使用令牌,向资源服务器申请获取资源
  10. -
  11. (F)资源服务器确认令牌无误,同意向客户端开放资源
  12. -
-

Access Token

-

Access Token,顾名思义,就是用来访问受保护资源要用到的令牌。客户端要访问资源服务器上受保护的资源,就必须要有 Access Token 作为通行证。Access Token 由授权服务器生成。客户端再获取了用户授权后才能想授权服务器申请 Access Token

-

An access token is a string representing an authorization issued to the client. … Tokens represent specific scopes and durations of access, granted by the resource owner, and enforced by the resource server and authorization server.

-

从官方的定义来看(RFC 6749 #section-1.4),Access Token 是一个字符串,至少要提供关于 客户端的基本信息(通常是客户端的 ID) 和该客户端获得的权限,权限有一组 scopes 表示,并且 Access Token 是有有效期的

-

关于 Access Token 的具体格式一个字符串标识符呢,还是自包含内容的信息呢,在 OAth2(RFC6749) 中并没有规定

-

Refresh Token

-

Refresh Token 也是有授权服务器生成,当一个 Access Token 过期或者失效时,客户端可以使用 Refresh Token 来获取一个新的 Access Token,这个新的 Access Token 拥有 scopes 范围小于等原来的那个 Access Token 权限

-

Refresh Token 是可选项,如果授权服务器生成了 Refresh Token,它会与 Access Token 一起返回给客户端,我们一起来看一看整个流程

-

refresh-token

-
    -
  1. (A)客户端通过向服务器镜像身份验证来请求访问令牌,授权服务器并显示授权
  2. -
  3. (B)授权服务器对客户端进行身份验证并验证权限授予,如果有效,则颁发访问令牌和刷新令牌
  4. -
  5. (C)客户端携带令牌向资源服务器发出受保护的资源请求
  6. -
  7. (D)资源服务器验证访问令牌,如果有效,返回受保护的资源给客户端
  8. -
  9. (E)重复步骤(C)和(D),直到访问令牌过期,如果客户知道访问令牌已过期,则跳至步骤(G);否则,它将发出另一个受保护的资源请求
  10. -
  11. (F)由于访问令牌无效,因此资源服务器返回无效的令牌错误
  12. -
  13. (G)客户端通过与进行身份验证来请求新的访问令牌,授权服务器并显示刷新令牌,客户端身份验证要求基于客户端类型和授权策略
  14. -
  15. (H)授权服务器对客户端进行身份验证并验证刷新令牌,如果有效,则发出新的访问令牌(并且,新的刷新令牌是可选)
  16. -
-

OAuth2 授权模式

-

关于 OAuth 的授权方式,可以在spring-security-oauth2-autoconfigure-2.1.2.RELEASE.jar jar 文件中

-
    -
  • 类:org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration
  • -
  • 方法:oauth2ClientDetails()
  • -
  • 参数设置:.setAuthrizedGrantTypes() 中 list 包含 -
      -
    • authorization_code
    • -
    • password
    • -
    • client_credentials
    • -
    • implicit
    • -
    • refresh_token
    • -
    -
  • -
-
-

由于标准的 OAuth2 协议中,授权模式并 不包括 refresh_token,但在 Spring Security 的实现中将其归为一种,因此如果需要实现 access_token的刷新,就需要这样一种授权模式

-
-

授权码模式

-

授权码(authorization_code)模式是功能最完整,流程最严谨的授权模式。它的特点就是通过客户端的服务器与授权服务器进行交互,国内常见的第三方平台登录功能基本都是使用这种模式

-

授权码模式

-
    -
  1. (A)用户访问客户端,后者将前者导向到认证服务器
  2. -
  3. (B)用户选择是否给予客户端授权
  4. -
  5. (C)假设用户给予授权,认证服务器将用户导向客户端事先指定的“重定向 URI”,同时附上一个授权码
  6. -
  7. (D)客户端收到授权码,附上早先的“重定向 URI”,向认证服务器申请令牌,这一步是在客户端的后台的服务器上完成,对用户不可见
  8. -
  9. (E)认证服务器核对了授权码和重定向 URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)
  10. -
-

密码模式

-

密码(password)模式是用户把账号和密码直接告诉客户端,客户端使用这些信息向授权服务器申请令牌。这需要用户对客户端高度信任,例如客户端和服务提供商是同一家公司

-

密码模式

-
    -
  1. (A)用户向客户端提供用户名和密码
  2. -
  3. (B)客户端将用户名和密码发给认证服务器,向后者请求令牌
  4. -
  5. (C)认证服务器确认无误后,向客户端提供访问令牌
  6. -
-

客户端模式

-

客户端(client_credentials)模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请权限。严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用的这种模式还是非常方便

-

客户端模式

-
    -
  1. (A)客户端向认证服务器进行身份认证,并要求一个访问令牌
  2. -
  3. (B)认证服务器确认无误后,向客户端提供访问令牌
  4. -
-

简化模式

-

简化(implicit)模式不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌,一般若网站是纯静态页面,则可以采用这种方式

-

简化模式

-
    -
  1. (A)客户端将用户导向认证服务器
  2. -
  3. (B)用户决定是否给予客户端授权
  4. -
  5. (C)假设用户给予授权,认证服务器将用户导向客户端指定的“重定向 URI”,并在 URI 的 Hash 部分包含了访问令牌
  6. -
  7. (D)浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值
  8. -
  9. (E)资源服务器返回一个网页,其中包含的代码可以获取 Hash 值中的令牌
  10. -
  11. (F)浏览器执行上一步获得的脚步,提取出令牌
  12. -
  13. (G)浏览器将令牌发给客户端
  14. -
-

OAuth2/OIDC 相关开源项目

-
    -
  • Redhat Keycloak(Java)
  • -
  • Apereo CAS(Java)
  • -
  • IdentityServer(C#)
  • -
  • OpenId-Connect-Java-Spring-Server
  • -
  • OAuth2全家桶项目
  • -
  • OAuth2全家桶项目
  • -
  • Apache Oltu + Shiro 实现 OAuth2 服务器
  • -
  • Using JWT with Spring Security OAuth
  • -
-

OAuth2 相关书籍

-
    -
  • OAuth2 in Action:主要讲述 OAuth2 协议的原理知识
  • -
  • OAuth 2.0 Cookbook:主要讲述 OAuth2 相关实践
  • -
  • Developer Guide
  • -
-

附录

-
    -
  • OAuth 2.0 最简向导 文章【需翻墙】
  • -
  • 传统 Web 应用中的身份验证技术
  • -
  • Spring Security OAuth 2.0 Roadmap Update
  • -
  • Okta Developer Platform
  • -
  • RFC6749 - The OAuth 2.0 Authorization Framework
  • -
-]]>
- - SpringCloud - - - SpringCloud - OAuth2 - -
- - SpringCloud(二)Security OAuth2 的 四种授权模式 - /2020/07/12/springcloud2/ - 在上一篇文章中,我们了解了 Security OAuth2 相关的一些基础知识,和整个四种授权模式的交互过程,那么本篇是对四种模式的实践,废话不多说,我们直接开始,SpringCloud 相关的实践代码均托管在rc-cluster-springcloud项目的中,项目使用的一些依赖版本如下

- -
    -
  • gradle:6.1.1
  • -
  • SpringBoot:2.2.6.RELEASE
  • -
  • SpringCloud:Hoxton.SR4
  • -
  • JDK:1.8
  • -
-

实践

-

在实践阶段为了方便,我将资源服务器和授权服务器整合在一个服务上,在后续扩展部分,会提供实际生产环境中的常用做法,先看项目的包依赖

-
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-oauth2'
-

下面的两点,不管是什么模式的授权方式,写法都是一样

-
    -
  1. 业务 API
    /**
    * 业务 API
    * 为了方便我直接将 UserInfo 对象放在了 Controller 类中
    *
    */
    @Controller
    public class UserController {

    @GetMapping("/api/userinfo")
    public ResponseEntity<UserInfo> getUserInfo() {
    User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    String email = user.getUsername() + "@gmail.com";
    UserInfo userInfo = new UserInfo();
    userInfo.setName(user.getUsername());
    userInfo.setEmail(email);
    // TODO 不同的授权选择不同的模式
    // 授权码模式
    userInfo.setGrantType("authorization_code");
    // 客户端模式
    userInfo.setGrantType("client_credentials");
    // 密码模式
    userInfo.setGrantType("password");
    // 简化模式
    userInfo.setGrantType("implicit");
    return ResponseEntity.ok(userInfo);
    }

    public static class UserInfo {

    private String name;
    private String email;
    private String grantType;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public String getEmail() {
    return email;
    }

    public void setEmail(String email) {
    this.email = email;
    }

    public String getGrantType() {
    return grantType;
    }

    public void setGrantType(String grantType) {
    this.grantType = grantType;
    }
    }
    }
    -
  2. -
  3. 资源服务
    /**
    * 资源服务器
    *
    * @author : Jerry xu
    * @date : 2020/7/17 23:45
    */
    @Configuration
    @EnableResourceServer
    public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {

    /**
    * 用来配置对资源的访问控制规则
    * 默认设置下,所有非 /oauth/** 路经下的资源都是被保护的
    *
    * @param http http
    * @throws Exception exception
    */
    @Override
    public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
    .anyRequest()
    .authenticated()
    .and()
    .requestMatchers()
    // 对 /api/** 路经下的资源进行了保护
    .antMatchers("/api/**");
    }
    }
    -
  4. -
-

权码模式

-

代码实现

-
授权服务器配置
-
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

@Resource
private BCryptPasswordEncoder passwordEncoder;

/**
* 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、
* 默认不支持 Resource Owner Password 授权类型,
* 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证
* 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法
* <ol>
* <li>authorization_code:授权码模式</li>
* <li>password:密码模式</li>
* <li>client_credentials:客户端模式</li>
* <li>implicit:简化模式</li>
* <li>refresh_token:刷新 token 模式</li>
* </ol>
*
* @param clients clients
* @throws Exception exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 在内存中,用于演示,不适用于实际生产环境
clients.inMemory()
// withClient + secret 这两个就是凭证
.withClient("clientapp")
.secret(passwordEncoder.encode("112233"))
// 重定向地址,用于授权成功后跳转
.redirectUris("http://localhost:9001/callback")
// 授权码模式
.authorizedGrantTypes("authorization_code")
// 权限细分
.scopes("read_userinfo", "read_contacts");
}
}
-
Security Web安全配置
-
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("jerry")
.password(passwordEncoder().encode("xyz"))
.roles("USER");
}
}
-

测试

-

整个过程请看视频

-

https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4

-
    -
  1. 获取授权
    # 浏览器请求地址
    http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo
    -
  2. -
  3. 使用授权码获取 Token
    -
  4. -
  5. 请求资源服务(业务请求)
    -
  6. -
-

如果你没有安装 Postman,对使用 curl 命令比较熟悉,那么可替换上面第 2,3 步操作

-
    -
  • 使用授权码获取 Token
    # 请自行更换 code 参数的值
    curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo"
    -
  • -
  • 请求资源服务(业务请求)
    # 请自行更换 authorization 参数
    curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530"
    -
  • -
-

-

客户端模式

-

代码实现

-
授权服务器配置
-
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

@Resource
private BCryptPasswordEncoder passwordEncoder;

/**
* 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、
* 默认不支持 Resource Owner Password 授权类型,
* 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证
* 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法
* <ol>
* <li>authorization_code:授权码模式</li>
* <li>password:密码模式</li>
* <li>client_credentials:客户端模式</li>
* <li>implicit:简化模式</li>
* <li>refresh_token:刷新 token 模式</li>
* </ol>
*
* @param clients clients
* @throws Exception exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 在内存中,用于演示,不适用于实际生产环境
clients.inMemory()
// withClient + secret 这两个就是凭证
.withClient("clientapp")
.secret(passwordEncoder.encode("112233"))
// 重定向地址,用于授权成功后跳转
.redirectUris("http://localhost:9001/callback")
// 客户端模式
.authorizedGrantTypes("client_credentials")
// 权限细分
.scopes("read_userinfo", "read_contacts");
}
}
-
Security Web安全配置
-
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("jerry")
.password(passwordEncoder().encode("xyz"))
.roles("USER");
}
}
-

测试

-

整个过程请看视频

-

https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4

-
    -
  1. 获取授权
    # 浏览器请求地址
    http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo
    -
  2. -
  3. 使用授权码获取 Token
    -
  4. -
  5. 请求资源服务(业务请求)
    -
  6. -
-

如果你没有安装 Postman,对使用 curl 命令比较熟悉,那么可替换上面第 2,3 步操作

-
    -
  • 使用授权码获取 Token
    # 请自行更换 code 参数的值
    curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo"
    -
  • -
  • 请求资源服务(业务请求)
    # 请自行更换 authorization 参数
    curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530"
    -
  • -
-

-

简化模式

-

代码实现

-
授权服务器配置
-
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

@Resource
private BCryptPasswordEncoder passwordEncoder;

/**
* 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、
* 默认不支持 Resource Owner Password 授权类型,
* 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证
* 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法
* <ol>
* <li>authorization_code:授权码模式</li>
* <li>password:密码模式</li>
* <li>client_credentials:客户端模式</li>
* <li>implicit:简化模式</li>
* <li>refresh_token:刷新 token 模式</li>
* </ol>
*
* @param clients clients
* @throws Exception exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 在内存中,用于演示,不适用于实际生产环境
clients.inMemory()
// withClient + secret 这两个就是凭证
.withClient("clientapp")
.secret(passwordEncoder.encode("112233"))
// 重定向地址,用于授权成功后跳转
.redirectUris("http://localhost:9001/callback")
// 授权码模式
.authorizedGrantTypes("authorization_code")
// 权限细分
.scopes("read_userinfo", "read_contacts");
}
}
-
Security Web安全配置
-
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("jerry")
.password(passwordEncoder().encode("xyz"))
.roles("USER");
}
}
-

测试

-

整个过程请看视频

-

https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4

-
    -
  1. 获取授权
    # 浏览器请求地址
    http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo
    -
  2. -
  3. 使用授权码获取 Token
    -
  4. -
  5. 请求资源服务(业务请求)
    -
  6. -
-

如果你没有安装 Postman,对使用 curl 命令比较熟悉,那么可替换上面第 2,3 步操作

-
    -
  • 使用授权码获取 Token
    # 请自行更换 code 参数的值
    curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo"
    -
  • -
  • 请求资源服务(业务请求)
    # 请自行更换 authorization 参数
    curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530"
    -
  • -
-

-

测试

-
http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=token&scope=read_userinfo&state=abc
-
http://localhost:9001/callback#access_token=60fbfadc-f801-4514-a5fb-52c6fd42f6cb&token_type=bearer&state=abc&expires_in=119
-

密码模式

-

https://github.com/spring-projects/spring-boot/issues/11136#issuecomment-381338605

-

代码实现

-
授权服务器配置
-
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

@Resource
private BCryptPasswordEncoder passwordEncoder;

/**
* 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、
* 默认不支持 Resource Owner Password 授权类型,
* 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证
* 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法
* <ol>
* <li>authorization_code:授权码模式</li>
* <li>password:密码模式</li>
* <li>client_credentials:客户端模式</li>
* <li>implicit:简化模式</li>
* <li>refresh_token:刷新 token 模式</li>
* </ol>
*
* @param clients clients
* @throws Exception exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 在内存中,用于演示,不适用于实际生产环境
clients.inMemory()
// withClient + secret 这两个就是凭证
.withClient("clientapp")
.secret(passwordEncoder.encode("112233"))
// 重定向地址,用于授权成功后跳转
.redirectUris("http://localhost:9001/callback")
// 授权码模式
.authorizedGrantTypes("authorization_code")
// 权限细分
.scopes("read_userinfo", "read_contacts");
}
}
-
Security Web安全配置
-
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("jerry")
.password(passwordEncoder().encode("xyz"))
.roles("USER");
}
}
-

测试

-

整个过程请看视频

-

https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4

-
    -
  1. 获取授权
    # 浏览器请求地址
    http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo
    -
  2. -
  3. 使用授权码获取 Token
    -
  4. -
  5. 请求资源服务(业务请求)
    -
  6. -
-

如果你没有安装 Postman,对使用 curl 命令比较熟悉,那么可替换上面第 2,3 步操作

-
    -
  • 使用授权码获取 Token
    # 请自行更换 code 参数的值
    curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo"
    -
  • -
  • 请求资源服务(业务请求)
    # 请自行更换 authorization 参数
    curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530"
    -
  • -
-

-

扩展

-

附录

-
    -
  • 芋道 Spring Security OAuth2 存储器
  • -
  • 浅谈 OAuth 2.0 (二) - 授权类型
  • -
  • Spring Security OAuth2
  • -
-]]>
- - SpringCloud - - - SpringCloud - OAuth2 - -
- - SpringCloud(三)Security OAuth2 源码分析 - /2020/07/25/springcloud3/ - ]]> - - SpringCloud - - - SpringCloud - OAuth2 - - - - 构建基础SSM框架 - /2018/05/20/ssm/ - SSM结构 -

SSM

- -

SSM框架整合

-

所谓的SSM即:Spring,SpringMVC,Mybatis

-
    -
  • Spring:一个轻量级的框架,有很多的拓展功能,最主要的我们一般项目使用的就是IOC和AOP。
  • -
  • SpringMVC:Spring实现的一个Web层,相当于Struts的框架,但是比Struts更加灵活和强大.
  • -
  • Mybatis:一个持久层的框架,在使用上相比Hibernate更加灵活,可以控制SQL的编写,使用 XML或注解进行相关的配置.
  • -
-

实战项目

-

ssm-practice

-

项目功能:

-
    -
  1. Spring,SpringMVC,Mybatis框架整合
  2. -
  3. Create Features
  4. -
  5. Retrieve Features
  6. -
  7. Update Features
  8. -
  9. Delete Features
  10. -
-
-

项目示例:rc-ssm

-
-

其他

-

ajax之PUT请求

-

客户端ajax方式发送PUT请求,Tomcat默认不会对请求进行处理;
-Tomcat:

-
    -
  1. 将请求体中的数据,封装成一个map
  2. -
  3. request.getParameter(“fileName”)就会从这个map中取值
  4. -
  5. springMVC封装POJO对象时,会把POJO中的属性的值,request.getParameter(“fileName”)
  6. -
-

解决方式:

-
    -
  • -

    方式一:Ajax发送POST请求
    -Ajax中type:“POST”
    -data: $(“”).serialize()+“&_method=PUT”

    -
  • -
  • -

    方式二:web配置中添加HttpPutFormContentFilter过滤器
    -1.HttpPutFormContentFilter将请求体中的数据解析包装成一个map
    -2.request被重新包装,request.getParameter()被重写,从自己封装的map中取出数据

    -
  • -
-

获取属性的值

-

prop修改和读取DOM原生属性的值
-attr修改和读取自定义属性的值

-]]>
- - Frame - - - Mybatis - Spring - SpringMVC - -
- - 复盘 2019 —— 安全上车 - /2020/01/22/summary-2019/ - - -
-
- - -
-
- -]]>
- - Summary - - - Summary - -
- - Git 同步 Fork 项目 - /2018/08/01/syncing-a-fork/ - Github 全球最大的同性交友网站,这里拥有最前沿的IT技术创新,拥有最流行的开源项目,等等…,总之这里是我的知识仓库,每天都会在上面寻找,学习知识

-

扯远了,本篇解决对于fork的项目,如何进行源项目的更新和同步问题

- -

远程仓库

-
    -
  1. 查看fork项目的远程仓库信息
    git remote -v
    origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
    origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
    -
  2. -
  3. 设置源项目仓库地址
    git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git
    -
  4. -
  5. 检查远程地址信息
    git remote -v
    origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
    origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
    upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (fetch)
    upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (push)
    -
  6. -
-

同步源仓库信息

-
    -
  1. 获取源仓库更新
    git fetch upstream
    remote: Counting objects: 75, done.
    remote: Compressing objects: 100% (53/53), done.
    remote: Total 62 (delta 27), reused 44 (delta 9)
    Unpacking objects: 100% (62/62), done.
    From https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY
    * [new branch] master -> upstream/master
    -
  2. -
  3. 查看本地master分支
    git checkout master
    Switched to branch 'master'
    -
  4. -
  5. 合并源仓库更新到本地master分支
    git merge upstream/master
    Updating a422352..5fdff0f
    Fast-forward
    README | 9 -------
    README.md | 7 ++++++
    2 files changed, 7 insertions(+), 9 deletions(-)
    delete mode 100644 README
    create mode 100644 README.md
    -
  6. -
-

同步源仓库branch

-

在git中master实质是一个特殊的branch,其它的branch的同步和master同步操作并不一样

-
# 查看项目的所有分支
git branch -v
# 当前项目在master分支,origin/HEAD类似指针,表示项目默认分支是origin/master
# origin/dev,origin/i18n,origin/ivan/feat-custom-lang,origin/master这四个分支是fork项目目前拥有分支
# upstream/dev,upstream/i18n,upstream/master表示源仓库项目所拥有的分支
* master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/i18n
remotes/origin/ivan/feat-custom-lang
remotes/origin/master
remotes/upstream/dev
remotes/upstream/i18n
remotes/upstream/master

# 切换到dev分支,同步源仓库dev分支到fork项目的dev分支
git checkout -b dev upstream/dev
# 推送修改到fork项目dev分支
git push origin dev
-
-

如果源仓库分支已被删除,那么可以在fork项目中删除源仓库已被删除的分支

-
-
# 删除指定分支,并推送到远程仓库
git push origin --delete branch_name
-

同步源仓库tag

-
# 获取源仓库的tag
git fetch upstream --tags
# 将新的的tag推送到fork项目
git push --tags
-

附录

-
    -
  • 同步你的 Fork 仓库
  • -
  • Configuring a remote for a fork
  • -
  • Syncing a fork
  • -
-]]>
- - Git - - - Syncing - -
- - 时间(一)之基础概念 - /2020/04/07/time1/ - -

时间是一种尺度,在物理定义是标量,借着时间,事件发生之先后可以按 过去-现在-未来 之序列得以确定(时间点),也可以衡量事件持续的期间以及事件之间和间隔长短(时间段) —— 维基百科

- - - -

- -

单位

-

时间的基本国际单位是。定义一秒为 铯-133原子 基态两个超精细能级间跃迁辐射振荡9,192,631,770周所持续的时间,其起点为世界时1958年的开始

-

时区

-

时区(Time Zone)是地球上的区域使用同一个时间定义。1884年在华盛顿召开国际经度会议时,为了克服时间上的混乱,规定将全球划分为24个时区(东西各 12 个时区)。造成时间上的混乱是由于世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差(这个偏差我们通常叫做时差)

-

图片来自维基百科

-

理论时区

-

理论时区以被 15 整除的经线为中心,向东喜两侧延伸 7.5°,即每 15°划分为一个时区。理论时区的时间采用其中央经线(或标准经线)的地方时。所以每差一个时区,区时相差一个小时,相差多少个时区,就相差多少小时

-

法定时区

-

为了避开国界线,有的时区的形状并不规则,而且比较大的国家以国家内部行政分机型为时区界线。例如,中国跨5个时区,但为了使用方便简单并且全国统一使用一个区时,实际上在中国使用东8区的区时一般称为北京时间作为标准时间

-

GMT

-

格林尼治平均时间(英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。
-自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。
-格林尼治标准时间的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时 基于天文观测本身的缺陷,已经被原子钟报时的协调世界时(UTC)所取代

-

UTC

-

协调世界时(英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称UTC)是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林威治标准时间。

-

中华人民共和国采用ISO 8601:2000的国家标准GB/T 7408-2005《数据元和交换格式 信息交换 日期和时间表示法》中亦称之为协调世界时。

-

协调世界时是世界上调节时钟和时间的主要时间标准,它与0度经线的平太阳时相差不超过1秒,并不遵守夏令时(由实验室用足够精确的铯原子钟导出的时间作为原子时,原子时的精确度极高,精度可以达到每2000万年才误差1秒)。协调世界时是最接近格林威治标准时间(GMT)的几个替代时间系统之一。对于大多数用途来说,UTC时间被认为能与GMT时间互换,但GMT时间已不再被科学界所确定。

-

DST

-

夏时制(英文:Daylight Saving Time),又称夏令时、日光节约时间,是一种在夏季月份牺牲正常的日出时间,而将时间调快的做法。通常使用夏时制的地区,会在接近春季开始的时候,将时间调快一小时,并在秋季调回正常时间。目前中国已经弃用DST

-

ISO-8601

-

国际标准ISO 8601,是国际标准化组织的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是2004年12月1日发行的第三版“ISO8601:2004”以替代1998年的第一版“ISO8601:1998”与2000年的第二版“ISO8601:2000”。

-

计算机中的时间

-

JSR-310

-

JSR(Java Specification Requests)是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。310 是一个编号,在 JDK8 中通过这个标准提供了新的改进日期时间的 API

-

相信做 Java 开发,对于JDK 的时间 API(小于JDK8版本)肯定都是吐槽不少,主要问题体现在以下几个方面

-
    -
  1. -

    最开始,Date 既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂
    -而后 JDK1.1 开始,将三项职责分开了

    -
      -
    1. 使用 Calendar 类实现日期和时间字段之间的转换
    2. -
    3. 使用 DateFormat 类来格式化和分析日期字符串
    4. -
    5. Date 只用来承载日期和时间信息
    6. -
    -

    尽管已经区分了各自的职责,但在使用时任然是很不方便

    -
  2. -
  3. -

    谜之 year 和 month

    -
    // 定义的月是 0-11表示 1-12 月

    Date date = new Date(2020, 4, 8);
    // 输出结果:Sat May 08 00:00:00 CST 3920
    // 年竟然是 3920 = 2020 + 1900
    // 月竟然是 May = 4 + 1
    System.out.println(date);

    Calendar calendar = Calendar.getInstance();
    calendar.set(2020, 4, 8);
    // 输出结果:Fri May 08 11:27:11 CST 2020
    // 年输出:和预想一致
    // 月输出:还和 Date 一样是输入月份 +1
    System.out.println(calendar.getTime());
    -
  4. -
  5. -

    Date 与 Calendar 类中的所有属性是可变的,线程不安全

    -
    // 计算两个日期之间的天数
    public static void main(String[] args) {
    Calendar birth = Calendar.getInstance();
    birth.set(1975, Calendar.MAY, 26);
    Calendar now = Calendar.getInstance();
    System.out.println(daysBetween(birth, now));
    // 连续计算两个 Date 实例的话,第二次会取得 0,因为 Calendar 状态是可变的
    System.out.println(daysBetween(birth, now)); // 显示 0?
    }

    public static long daysBetween(Calendar begin, Calendar end) {
    long daysBetween = 0;
    while(begin.before(end)) {
    begin.add(Calendar.DAY_OF_MONTH, 1);
    daysBetween++;
    }
    return daysBetween;
    }
    -
  6. -
-
-

JSR 310的规范领导者 Stephen Colebourne,同时也是 Joda-Time 的创建者,JSR 310是在Joda-Time的基础上建立的,参考了绝大部分的API

-
-

2038年问题

-

图片来自维基百科

-

现时大部分使用UNIX的系统都是32位的,即它们会以32位有符号整数表示时间类型time_t。因此它可以表示136年的秒数。表示协调世界时间1901年12月13星期五20时45分52秒至2038年1月19日3时14分07秒(二进制:01111111 11111111 11111111 11111111,0x7FFF:FFFF),在下一秒二进制数字会是10000000 00000000 00000000 00000000(0x8000:0000),这是负数,因此各系统会把时间误解作1901年12月13日20时45分52秒(亦有可能回归到1970年)。这时可能会令软件发生问题,导致系统瘫痪

-

当前的解决方案
-把系统由32位转为64位系统。在64位系统下,此时间最多可以表示到292,277,026,596年12月4日15时30分08秒

-

附录

-
    -
  • 时间 • 维基百科
  • -
  • 时区 • 维基百科
  • -
  • UNIX时间 • 维基百科
  • -
  • ISO 8601 • 维基百科
  • -
  • 格林尼治标准时间(GMT) • 维基百科
  • -
  • 协调世界时(UTC) • 维基百科
  • -
  • 计算机世界中的时间概念
  • -
  • JSR 310: Date and Time API
  • -
  • JSR310-新日期API(完结篇)-生产实战
  • -
-]]>
- - JDK8 - - - JDK8 - Time - -
- - 时间(二)之核心类 - /2020/04/08/time2/ - 上一篇时间(一)文章,我们已经了解学习了时间相关的一些概念和时间相关的其他知识,那么本篇文章开始我们深入在计算机领域中关于时间的相关核心类的知识,先来让我们看看 JSR-310(JDK8+) 和之前(JDK7 之前)的比较

-

- -

JDK7

-

Date

-

Date 是 Java 最早提供用来封装日期时间的类,由于不易于国际化且参数计算不符合日常认知或不正确,很多获取年、月、日、小时等数据的方法都已废弃(@Deprecated),被 Calendar 类的方法替代

-

Date 类有两个关键的成员变量

-
// 记录当前时间戳
private transient long fastTime;

/*
* If cdate is null, then fastTime indicates the time in millis.
* If cdate.isNormalized() is true, then fastTime and cdate are in
* synch. Otherwise, fastTime is ignored, and cdate indicates the
* time.
* cdate 对象是 BaseCalendar.Date 类,继承自 sun.util.calendar.CalendarDate 包含很多已计算好的日期时间相关变量,如 dayOfWeek(所在星期的第几天),leapYear(是否闰年)等
* 如果 cdate 对象为空,用 fastTime 变量代表精确到毫秒的时间
* 如果 cdate.isNormalized() 方法返回 true,则 fastTime 和 cdate 已经同步过
* 如果 cdate.isNormalized() 方法返回 false,则忽略 fastTime 的值,使用 cdate 代表时间
*/
private transient BaseCalendar.Date cdate;
-

Date 类提供了两个构造函数

-
// 无参构造方法,创建当前时间的 Date 类
public Date(){
this(System.currentTimeMillis());
}

// 传入一个 Unix 时间戳,创建特定的时间 Date 类
public Date(long date){
fastTime = date;
}

// 其他通过年月日创建的构造方法已被 Calendar.set() 和 DateFormat.parse() 等方法替代
-
-

静态方法 System.currentTimeMillis() 返回 UTC 时间从 1970-01-01 00:00:00 到现在的总毫秒数,返回类型为 long,也是我们最常见获取当前时间的方法

-
-

java.sql.Datejava.sql.Timejava.sql.Timestamp 类都继承自 java.util.Date 类,是专门用于数据库连接的,由于是继承关系,从数据结构来看它们和父类的区别不大。

-

主要区别在于 Timestamp 类可以表示至纳秒级,其 fastTime 字段从秒之后被截断,毫秒至纳秒精度保持在特有的 nanos 字段中,需要注意 Timestamp 类的纳秒进度可能是 假的

-
package java.sql.Timestamp;

/**
* Constructs a Timestamp object
* using a milliseconds time value. The
* integral seconds are stored in the underlying date value; the
* fractional seconds are stored in the nanos field of
* the Timestamp object.
*
* @param time milliseconds since January 1, 1970, 00:00:00 GMT.
* A negative number is the number of milliseconds before
* January 1, 1970, 00:00:00 GMT.
* @see java.util.Calendar
*/
public Timestamp(long time) {
super((time/1000)*1000);
nanos = (int)((time%1000) * 1000000);
if (nanos < 0) {
nanos = 1000000000 + nanos;
super.setTime(((time/1000)-1)*1000);
}
}
-

可以看出,在 fastTime 字段强行截断后,进行毫秒值直接乘以 1000000 的操作后赋给了 nanos 字段,成为了 “只能表示到毫秒的纳秒级精度” 。当然,还可以通过 setNanos(int n) 方法给纳秒数赋精确值。

-

虽然从数据结构上看没有什么特别,但如果涉及到 Timestamp 类的父子类型转换或时间比较,就需要注意这里的“坑”

-
    -
  1. -

    equals() 方法的不对称性 java.sql.Timestamp 类和其父类 java.util.Date 的 equals() 方法是不符合对称性

    -
    public static void main(String[] args){
    Data date = new Date();
    Timestamp timestamp = new Timestamp(date.getTime());
    System.out.println(date.equals(timestamp));
    System.out.println(timestamp.equals(date));
    }

    // 输出结果
    true
    false
    -
  2. -
  3. -

    时间比较类方法的 “异常” 现象如下,两个有毫秒之差的时间点,after() 方法返回不符合客观事实

    -
    public static void main(String[] args){
    Long current = System.currentTimeMillis();
    Date t1 = new Timestamp(current);
    Date t2 = new Timestamp(current + 1);
    System.out.println(t2.after(t1));
    System.out.println(t2.compareTo(t1) > 0);
    }

    // 输出结果
    false
    true
    -
  4. -
-

如果在不确定类型的情况下进行时间的比较,尽量使用 compareTo() 方法,可以保证正确性

-

Calendar

-

Calendar 类是一个日历抽象类,提供了一组对年月日时分秒星期等日期信息操作的函数,并针对不同国家和地区的日历提供了相应的子类,即本地化(比如:公历【GregorianCalendar】,佛历【BuddhistCalendar】,日本历【JapaneseImperialCalendar】等)

-

从 JDK1.1 版本开始,在处理日期和时间时系统推荐使用 Calendar 类进行实现。在设计上,Calendar 类的功能要比 Date 类强大太多,而且在实现方式上也比 Date 类要复杂些

-

Calendar 类可以通过静态工厂方法或 new 子类的方式来获得实例

-
    -
  1. -

    getInstance() 方法,有四个重载方法,参数是时区和地区,如果不传会取服务器默认的时区和地区(地区的出现是专门为了区分泰国和日本)

    -
      -
    • getInstance()
    • -
    • getInstance(TimeZone zone)
    • -
    • getInstance(Locale aLocale)
    • -
    • getInstance(TimeZone zone, Locale aLocale)
    • -
    -
  2. -
  3. -

    新建子类对象

    -
    Calendar calendar = new GregorianCalendar();
    -

    Calendar 类可以实现带时区的年月日时分秒星期等对 Unix 时间戳的转换,内部通过子类复杂的 computeTime() 方法进行计算。

    -
      -
    • -

      使用 getTime() 方法返回 java.util.Date 类型的时间

      -
    • -
    • -

      使用 getTimeInMillis() 方法返回当前 Unix 时间戳

      -
    • -
    • -

      通过 get(int field) 方法获取其他年月日等单独信息

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      常量含义
      Calendar.YEAR年份
      Calendar.MONTH月份
      Calendar.DATE日期
      Calendar.DAY_OF_MONTH日期,和上面字段意义完全相同
      Calendar.HOUR12 小时制的小时
      Calendar.HOUR_OR_DAY24 小时制的小时
      Calendar.MINUTE分钟
      Calendar.SECOND
      Calendar.DAY_OF_WEEK星期几
      Calendar.DAY_OF_YEAR今年的第几天
      -
    • -
    -

    也可以通过多个 set 重载方法设定各种值,同时,add() 方法支持对单个值的加减,从而实现时间推移的计算,传入负数即为减

    -
    public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    System.out.println("当前日期: " + calendar.getTime());
    calendar.add(Calendar.DATE, 10);
    System.out.println("日期+10: " + calendar.getTime());
    calendar.add(Calendar.DATE, -5);
    System.out.println("日期-5: " + calendar.getTime());
    }

    // 输出结果
    当前日期: Fri Apr 10 15:37:46 CST 2020
    日期+10: Mon Apr 20 15:37:46 CST 2020
    日期-5: Wed Apr 15 15:37:46 CST 2020
    -
  4. -
-

GregorianCalendar 对象可以直接使用 isLeapYear(int year) 接口判断是否闰年。需要注意两点

-
    -
  1. Calendar 中 MONTH 的范围: 0-11(Calendar.JANUARY - Calendar.DECEMBER),因此为避免使用错,Calendar.JANUARY 来表示一月
  2. -
  3. Calendar 中 DAY_OF_WEEK 星期日是 1,并不是我们习惯的星期一是 1
  4. -
-

SimpleDateFormat

-

SimpleDateFormat 是一个以语言环境敏感的方式来格式化和分析日期的类,SimpleDateFormat允许选择任何用户自定义的日期时间格式来格式化

-
public static void main(String[] args){
Date date = new Date();
System.out.println("格式化之前: " + date);
SimpleDateFormat ft = new SimpleDateFormat("z yyyy-MM-dd HH:mm:ss");
System.out.println("格式化之后: " + ft.format(date));
}

// 输出结果
格式化之前: Fri Apr 10 15:55:42 CST 2020
格式化之后: CST 2020-04-10 15:55:42
-

字符含义

-

日期和时间格式由日期和时间模式字符串中的日期和时间模式字符串指定,从 AZ 和从 az 的无引号字母被解释为表示日期或时间字符串组件的模式字母。可以使用单引号'引用文本以避免解释。"''"表示单引号。所有其他字符都不会被解释;它们只是在格式化期间复制到输出字符串中,或者在解析期间与输入字符串匹配。

-

定义了以下模式字母(所有其他字符AZa - z 保留),详细说明见 SimpleDateFormat 类 Java Doc 中注释明

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
字母日期或时间组成说明示例
GEra designator【是一个代号】TextAD
y(小写)Year【年】Year1996; 96
YWeek year【周年】Year2009; 09
MMonth in year (context sensitive)【一年中的月份(上下文相关)】MonthJuly; Jul; 07
LMonth in year (standalone form)【一年中的月份(独立形式)】MonthJuly; Jul; 07
w(小写)Week in year【一年中的第几周】Number27
WWeek in month【每月的周】Number2
DDay in year【一年中的一天】Number189
d(小写)Day in month【每月的一天】Number10
FDay of week in month【每月的星期几】Number2
EDay name in week【星期几】TextTuesday; Tue
u(小写)Day number of week (1 = Monday, …, 7 = Sunday)
【星期几(1 =星期一,…,7 =星期日)】
Number1
a(小写)Am/pm marker【上午/下午标记】TextPM
HHour in day (0-23)【一天中的小时,0-23表示】Number0
k(小写)Hour in day (1-24)【一天中的小时, 1-24表示】Number24
KHour in am/pm (0-11)【上午/下午 0-11表示】Number0
h(小写)Hour in am/pm (1-12)【上午/下午 1-12表示】Number12
m(小写)Minute in hour【一小时内】Number30
s(小写)Second in minute【分钟】Number55
SMillisecond【毫秒】Number978
z(小写)Time zone【时区】General time zonePacific Standard Time;
PST; GMT-08:00
ZTime zone【时区】RFC 822 time zone-0800
XTime zone【时区】ISO 8601 time zone-08; -0800; -08:00
-

格式化示例

-

给定太平洋时间,2001-07-04 12:08:56,以下示例展示按照自定的格式显示时间

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
日期和时间模式结果
“yyyy.MM.dd G ‘at’ HH:mm:ss z”2001.07.04 AD at 12:08:56 PDT
“EEE, MMM d, ''yy”Wed, Jul 4, '01
“h:mm a”12:08 PM
“hh ‘o’‘clock’ a, zzzz”12 o’clock PM, Pacific Daylight Time
“K:mm a, z”0:08 PM, PDT
“yyyyy.MMMMM.dd GGG hh:mm aaa”02001.July.04 AD 12:08 PM
“EEE, d MMM yyyy HH:mm:ss Z”Wed, 4 Jul 2001 12:08:56 -0700
“yyMMddHHmmssZ”010704120856-0700
“yyyy-MM-dd’T’HH:mm:ss.SSSZ”2001-07-04T12:08:56.235-0700
“yyyy-MM-dd’T’HH:mm:ss.SSSXXX”2001-07-04T12:08:56.235-07:00
“YYYY-'W’ww-u”2001-W27-3
-

TimeZone

-

JDK8+

-

Clock

-

Instant

-

LocalDate

-

LocalTime

-

LocalDateTime

-

OffsetTime

-

OffsetDateTime

-

ZonedDateTime

-

ZoneId

-

Year

-

Month

-

DayOfWeek

-

MonthDay

-

YearMonth

-

总结

-

附录

-
    -
  • Java SE 8 日期和时间
  • -
  • 循序渐进解读计算机中的时间—应用篇(上)
  • -
  • 循序渐进解读计算机中的时间—应用篇(下)
  • -
  • JSR310新日期API(二)-日期时间API
  • -
  • Java中日期时间相关核心类
  • -
  • 《Java 8 实战》● 第 12 章
  • -
-]]>
- - JDK8 - - - JDK8 - Time - -
- - 时间(三)之格式化解析和时间计算 - /2020/04/09/time3/ - -

总结

-

附录

-
    -
  • Convert Date to LocalDate or LocalDateTime and Back
  • -
  • JSR310新日期API(三)-日期时间格式化与解析
  • -
  • Java中日期时间相关核心类
  • -
-]]>
- - JDK8 - - - JDK8 - Time - -
- - 时间(四)之主流框架中使用 - /2020/04/10/time4/ - -

总结

-

附录

-
    -
  • Java SE 8 日期和时间
  • -
  • JSR310新日期API(二)-日期时间API
  • -
  • Java中日期时间相关核心类
  • -
-]]>
- - JDK8 - - - JDK8 - Time - -
- - 重要说明 - /2022/06/05/top1/ - 距离上一次更新文章已经过去了 1 年多了,时间可过的真快。原计划将现在的主站点要进行按照领域划分,将平时工作中遇到的、实践的技术整理到对应的领域,方便快速查找,提供一个沉浸式的学习知识体验,但由于个人也是一个懒癌拖更患者,加之在过去的一年工作中处在新领域,学习了很多技术,很多笔记还没有整理完善,因此没有及时更新博客,同时最近也遇到了站点无法正常访问,经过一系列的排查,发现了是由于开源的 CDN 提供方 jsdelivr 被污染了 DNS,因此不得不先更新站点的基础服务,以便能正常访问

- -
-

话说,国内的网络环境真的是一言难尽,不得不说在技术、技术基础设施、技术思想等发展道路上任重道远

-
-

对于个人几个主要的站点,规划如下

-
    -
  1. incoder.org: 作为分享生活、感悟、个人状态为主的地方,偶尔汇总某些技术等的综合性文章
  2. -
  3. backend.incoder.org: 记录以 Java 为基础的后端开发生态技术领域
  4. -
  5. mobile.incoder.org: 记录以原生开发为基础的移动端开发生态技术
  6. -
  7. incoder.app: 记录个人开源应用
  8. -
-

后续会迁移本站点部分文章到具体的领域站点

-
-]]>
- - Top - - - Top - -
- - 长沙 - /2020/05/01/travel-cs/ - ]]> - - Memory - Travel - - - 长沙 - - - - 川藏行 —— 人在囧途 - /2020/10/13/travel-cz-feel/ - 这次假期安排算是毕业后,除去 2020 年春节因为疫情,假期最长的一个,满脑子写着高兴,以至于我出门前一刻,还在处理工作上的事情,浑浑噩噩的追下午的动车和好友汇合,哪知道这只是囧途的开始,在地铁上我快要困死过去,还好没有坐过站,之前有看一眼车站,但是并没有仔细看具体是哪个站,在我的潜意识里一直认为是东站,当我冲到检票口时,我刷身份证发现进不了站,工作人员说我跑错站,我当时一脸茫然(心想跑错站也没关系,只要它经过东站就可以),赶紧查看我的购票记录,发现自己真是跑错了车站而且还不经过东站,距离发车时间就剩 1 小时了

- -

计算了下地铁和检票时间,得出现在赶过去时间非常紧,万一中途出现什么状况,我肯定要凉凉,立马决定改签到东站出发的列车,但是我这次购票是用的 12306 行程积分兑换的,不能直接在手机上进行改签或退票,于是我就赶忙拖着行李去往 1 楼的人工服务窗口,意外的是我恰巧跑向了东边的人工窗口,到了东边绕了一圈发现今天东边窗口不提供服务,于是乎我又从东边一路小跑到西边窗口,今天并不是周末,但人也是不少在排队,我必须在 1 小时之内改签车票,否则我当时购买的车次发车的话,我必须得在原车票的起点站去改签,而原车站后面到达目的地的时间太晚,我根本赶不上飞机(〒︿〒)

-

这次看清了对应业务的办理窗口,我找到其中一个进行排队,焦急地等待着,不清楚前面那位朋友是什么状况,办理他一个人的时间足足等了 10 多分钟,一边看着手机查询车次,一边盯着时间,眼看就要到我了,那工作人员却说暂停服务,她要去趟卫生间,我说我很赶时间,她没理会我,此时的我进退两难,去排另一个队,时间明显不够,我也没办法去插队,两眼一直盯着在我前方的窗口,还好还好,工作人员大概耽误了 3 分多钟回到岗位上,帮我办理改签,由于我当时兑换的是动车,现在发车时间最近的一趟是高铁,需要再补 3000+积分,还好我的积分还够,那还等什么,改改改,改签完成后长舒一口气,再晚 10 分钟,我要么去原发车站去改签,要么重新购买在东站购买车票(此时我并不能在手机上购买车票,因为原行程和我从当前位置出发的行程是冲突的,必须得在会员人工窗口办理退票)

-

12306

-

12306会员积分使用规则

-

12306会员积分使用规则

-

12306车票改签规则

-

12306车票改签规则

-

12306车票退票规则

-

12306车票退票规则

-

改签完车票后,距离发车时间还有 1 个小时,我赶忙在候车厅二楼找到一个饭馆,去点了些东西,吃着东西,一个电话打断了我囫囵吞枣的状态,说是项目线上环境,某页面数据加载不出来,让我看看是什么原因,是不是我中午出发前改的东西影响了,我一脸问号,因为我改动的并没有涉及到页面加载慢的地方,那边说下午要线上演示,让我抽空看看处理下,我连忙说我尽量。距离检票时间还是有 30 多分钟,我放下了手里的鸡腿,开启手机热点,打开电脑,盯着屏幕今天的提交记录,一遍遍排查,自己的没有做相关的操作,当前我也没有心思定下心来找问题的根源,想着先把超时时间调大些,先保证能加载出来数据,至于慢的问题,我到达目的地后再处理,三下五除二调整后,发布测试,并没有什么用😔,于是继续找到对应 API 服务,把对应的服务接口查询做了下优化处理,想让服务正常使用,处理调试后,发现可以接受,来不及多想,赶忙收拾东西去检票上车,一路狂奔

-

到达中转站,和好友汇合,终于停下来了,接过带给我的心心念念的的芋泥奶茶,我就不客气的解决掉它,别喝边说着,快一年没见,怎么又长高了,变瘦了,你都偷偷补了啥,她弱弱的回了句,我不挑食的,我一脸尴尬,她知道我挑食,不吃肉(尤其是外面,老妈做的肉,我还是能吃点),我赶忙说到,我在努力克服这个了,现在还是能稍微吃一点的,两人在候机厅断断续续有一搭没一搭的说着今年发生的事情,说着说着,突如其来的语音播报,由于疫情影响,我们所乘航班推迟,还好没有说是取消,不然今天我要囧到家😶,按照要求填写好行程路径,在线领取健康码后(为啥全国的健康码还没统一,一脸嫌弃,不前一阵子说健康码全国通用嘛),延迟 40 分钟左右,终于登上了前往成都的飞机,在一路的折腾,飞机起飞后,夜景也没来得急看我就睡着了,到达成都后才被朋友叫醒

-

天府之国 - 成都

-

在这里,每件事或物都能和国宝相连,要是这里生活的话,我居然有我也是“国宝”的错觉,哈哈哈,突然有点厚脸皮了,谁让这家伙那么可爱,第一天就把市区能去的都逛了个七七八八,到底是 3 个年轻人,身体真好,哎,都没有停下来好好看看成都的美女帅哥,都光顾着吃喝和看风景了,话不多说,先来几张图片缓和下气氛,写到这里我又想起了蛋烘糕,想吃,想起了又辣又麻的椒麻鸡,想吃,想起了兔头(真是让人秃头,兔兔那么可爱,为啥要吃兔兔,其实我更喜欢兔子肉,哈哈哈),想吃。啊好难受,都想吃,我觉得我如果待在这个城市,一定能长胖
-
-
-
-
-
-皂片有点多,就放这几张吧,看看就好,后面两天是不是下点小雨,阴蒙蒙的天气就和我当时的心情对应上了,因为我还要修改问题,前面留下的坑,还要去填,真的,真的,真的我不是故意要把工作的事情带到旅途中来的,下次我一定不带电脑了,打死不带,刀架到我脖子也不带。让某位同学一整天都在家里刷剧,真是罪过罪过(虽然你曾经这里待了很久,尝遍了这里美食,看尽了这里的美景,但这不一样,因为和你同看风景,同品美食的是不同的人,不同的心情)

-

话说回来玉林街道那里好吃的真多,那个谁自己要控制住,不然想瘦真的有点难,话说回来在成都的 3 天虽然有亿点点的不完美,但是正是由于这些不完美,才让我的记忆更加深刻。按照网上所谓的高评分美食,走路老远的路过去,发现吃完并没有想想中那么好吃(我严重怀疑他们是刷单刷出来的);不看清目的地营业时间,跑去傻傻的在门口不知道该往哪去(告诫我下次做规划这种事情一定提前要有备用计划);一起搭公交车去很远的地方买甜点,虽然有点小远,但是值得。

-

不知道下次会是什么时候再去成都了,也不知道下次身边会是谁,能不能像现在一样能聚在一起。我想的可真远,大白天发什么神经,还是过好眼前的,得嘞……珍惜现在拥有的

-

三天后,一行三人出发,前往西藏拉萨。我们并没有搭乘火车,而是选择了飞机,谁让这价格那么香,还能节省接近 2 天的时间。抵达拉萨后,并没有出现不良的反应

-

日光之城 - 拉萨

-

引入眼帘给我的第一感觉是蓝,蓝的彻彻底底,没加半点人工滤镜渲染,也没 AI 换天,这里随手拍照都能拍到非常好看的图片,不需要修图,这里的云也很配合,像是一种出淤泥而不染的干净,在蓝色背景的映衬下,云朵就像是在你的手边随手都可以去摘下来捧在手里,话不多说,我们先来看拉萨的牌面,大气恢弘的建筑俯瞰整个拉萨市(标准的游客拍照)
-
-第一天就头疼的么,不存在的,这里海拔也不过 3600 左右,还是能接受的,只不过快步走或跑步是真的比内陆容易喘气。在高原地带由于氧气稀薄,汽车大部分的燃油都没有充分燃烧,尾气直接排进空气,这种味道会让你欲仙欲死。四天的行程已提前被安排,跟着导游,听他安排

-

第二天一大早就集结,开始 4 天的行程,行程结束后再回到拉萨市区,迎着朝阳汇聚了来自不同地方 12 个小伙伴,接下来的这几天我们这些人就是一个小群体,彼此互相照顾,所幸我们所在的团队中年龄都不大,都是年轻小伙和年轻姑娘,大家之间也没什么隔阂,很快就能玩耍到一块去,下图是 07:35 这个点,这里的天还没有完全亮
-

-

我们行程的这几天天气非常好,都是蓝天白云,刚开始不停的按下快门,想要记录下这让人心旷神怡的美景,生怕自己错过了最美的时刻,但到后来都有些麻木了,原因是你只要抬头就能看到,根本拍不过来,索性就好好沉浸其中,好好呼吸这有点甘甜的空气
-
-

-

当蓝天配上纳木错的美景,是一种秀色可餐的满足感
-
-

-

当蓝天被雄鹰所占领,这就是鹰击长空现实版
-

-

当所有的疲惫和不适到了珠峰大本营,都会被抛之脑后,因为我已深深被这傲视群雄的孤独所吸引,第一次与世界最最高峰是这么的近
-

-
-

照片实在是有点多,继续挖坑后面整理成相册再放出来

-
-

上一次超过 30H 的行程,那会刚毕业,一路上总能听到各式各样千奇百怪的故事,和遇到一些形形色色的路人,因为那时候,在火车上经常没有信号,大家会一起唠唠嗑,我就听着这样乱七八糟的故事和别人经历的事,不想听了就翻开自己带的书,就这样度过那漫长的行程。而现在网络普及,信号增强了,但是人与人的交流好像都被转移到线上了,在一块面对面交流的人少了,故事也都来源于那块屏幕,隔着屏幕说话,科技的初衷是提高我们的生活质量和方便我们的生活,而不是隔断人与人的面对面交流

-

总结

-

-
-

有形的东西迟早会凋零,但只有回忆是永远不会凋零的

- -
-
    -
  1. 好好学一下摄影和构图吧,总会用得上
  2. -
  3. 要把快乐传染给周围的人
  4. -
  5. 有话就要说,别一个人闷在心里
  6. -
  7. 把想到的感受到的及时记录下来,有时候灵感,想法就是那么昙花一现
  8. -
  9. 多吃点肉吧,不然风一吹朋友就找不到我了
  10. -
-]]>
- - Memory - Travel - - - 成都 - 拉萨 - -
- - 川藏行 —— 行程规划 - /2020/09/13/travel-cz-plan/ - 唠叨了很久的《成都》,这次终于要去见一见你了,成都在我的印象中是一个万物皆可辣,但也少不了让你肉跳的麻,那里有让你每天不重样的美食,有看不完的美景,也有能让你安逸的歌谣《成都》。早在去年十一就被《盛世中华》视频种草,时隔一年,这个心愿要就要被实现,这次的十一目标不仅是四川,还有西藏的风景,这是双份的快乐。

-

曾经记一个人是一篇文章,后来记一个人是一首歌,后来记一个人是一门学科,再后来记一个人是一段代码或语言,到现在记一个人是一个城市,因为那是有你的城市,那里有你的故事,好了废话有点多,先来看看这次出行的规划

- -

四川

-

总计 3 天时间(2020.09.26 —— 2020.09.28),尽管《盛世中华》中的风景很迷人,但并不在此次行程安排中,主要是考虑到这些景点都比较远,且并不是成熟的游玩路线,这次去的地方主要是视频中没有的景点

-

DAY 1

-

第一天主要是在成都市区内游玩,景点包括:锦里,杜甫草堂博物馆,人民公园,以及四川大学望江校区或者她曾读书的西南财经,其实这都不是重点,重点是去吃各种美食(The Sense 醒食,無早 bowl,老龙亭,还有各种小吃和必吃的火锅),对了还要见一个 3 年多未见的老友(团座)

-

DAY 2

-

第二天去一个稍微远点的地方,乐山大佛,小时候看《风云》时还以为那尊大佛是电脑特效,后来才知道这是真的,那我可要去那里找一找有没有聂风留下的雪饮刀,和菩提果,修炼我的武功。如果有时间去爬一爬峨眉山,去看看令狐冲是否还在那里带领峨眉派,那里有没有周芷若留下倚天剑的残骸

-

DAY 3

-

第三天安排上国宝,这圆滚滚的大家伙,好想用手 rua rua 它的毛,行程结束后再去 mars 上推荐的书店(文轩 BOOKS,無早 bookstore),在那里看一看书,吃一吃甜点,品一品生活,晚上可以去建设路,去感受下城市烟火

-

拉萨

-

共计 8 天(2020.09.29 —— 2020.10.06),拉萨由于是非常陌生,因此选择跟团游的方式,这样既节省时间又省钱(我只是一个搬砖工人),因此选择了 5 天 4 夜(DAY 2 —— DAY 6)的跟团游玩,极大可能是走马观花式标准的游客方式

-

路线参考

-
    -
  • 以拉萨为中心,由近及远,最负盛名的纳木错(路程较远,往往需要在湖边村镇留宿一晚)和羊卓雍措(可当他往返)
  • -
  • 向南,山南人文线,相对比较小众,山南是西藏的“旧都”,人文圣地估计众多,包括西藏第一个真正意义上的寺院:桑耶寺,常见的路线是 2 天,包含了拉姆拉错,思金拉错以及格鲁三大寺当中最古老的甘丹寺(在拉萨达孜县)
  • -
  • 向东去往林芝,这里衔接川滇,海拔较低,森林茂盛,有西藏小江南美陈,全程会路过巴松措,鲁朗林海,雅鲁藏布江(峡谷),南迦巴瓦峰,3 天的行程,再加 2 天可抵达波密,然乌湖
  • -
  • 向西南是日喀则,这里是后藏的中心,古今地位仅次于拉萨,除了在此拜访扎什伦布斯之外,跟多人选择来到这得目的是向南 200 公里走进珠峰大本营,去看一下世界第一高峰,常规路线会带上纳木错,往返总计 5 天
  • -
  • 向西北,是“西藏的西藏”阿里一线,人迹罕至,痛并快乐,经过纳木错穿越羌塘无人区,抵达狮泉河,冈仁波齐,古格王朝,玛旁雍错,常规路线 7—10 天
  • -
-
-

贪心路线,从拉萨出,去程走阿里线,回程走日喀则线,一个西部大环线,耗时 13—15 天

-
-

当然上面是整个拉萨可以游玩的框架,而我们小团队并不是和上图完全一致,只是其中的一部分

-

DAY 1

-

由于是第一次来高原地带,所以第一天就不进行大运动量的活动,就在拉萨市区走一走,了解藏族风土人情,大致包括以下:布达拉宫,文成公主,八廓街,大昭寺,总之看心情和看身体状况来灵活应变

-

DAY 2

-

行程安排:拉萨 - 念青唐古拉山 - 纳木错圣象天门日出日落 - 住圣象天门
-生活住宿:早餐自理,午餐、晚餐含,住营地帐篷或附近民宿(住宿环境别抱太大幻想)
-重点关注:纳木错圣象天门

-

DAY 3

-

行程安排:圣象天门 - 那根拉山口 - 藏北草原 - 东古拉山 - 尼木 - 日喀则
-生活住宿:早餐、午餐、晚餐含,住三星酒店
-重点关注:藏北草原

-

DAY 4

-

行程安排:日喀则 - 定日 - 嘉措拉山口/加乌拉山口 - 珠穆朗玛国家公园 - 珠峰大本营 - 民宿/营地帐篷
-生活住宿:早餐、午餐、晚餐含,住营地帐篷或附近民宿(住宿环境别抱太大幻想)
-重点关注:珠峰大本营的星空

-

DAY 5

-

行程安排:定日 - 扎什伦布寺 - 日喀则
-生活住宿:早餐自理,午餐、晚餐含,住三星酒店
-重点关注:日喀则

-

DAY 6

-

行程安排:日喀则 - 卡若拉冰川 - 羊卓雍措 - 拉萨
-生活住宿:早餐、午餐含,晚餐自理,晚上返回拉萨
-重点关注:羊卓雍措

-

DAY 7 —— DAY 8

-

这两天是自由时光,没有特殊的安排

-

注意事项

-

由于此次去往西藏相对比较偏远,加之藏族人民的生活语言等和我们都不一样,生活环境和其他地方相差较大,异常在出行前带好一些必备的用品,

-

行李指南

-
    -
  1. 证件,一次性洗漱用品,水杯等
  2. -
  3. 阿咖酚散与葡萄糖粉,有效缓解高反,晕车药/贴,感冒发烧,消炎等药物
  4. -
  5. 防风防雨的衣服很有必要,羽绒服,保暖衣物等
  6. -
  7. 进藏四件套:帽子,墨镜,防嗮,唇膏
  8. -
  9. 相机等电子设备
  10. -
  11. 带一些速食,比如泡面,辣条,自住小火锅,矿泉水等
  12. -
-
-

可根据自身需要合理的补充

-
-

何时进藏

-
    -
  1. 6-8 月份为西藏雨季,景色会打折扣
  2. -
  3. 最佳:9-10 月(秋叶),3-4 月(春桃)
  4. -
-

交通方式

-
-

根据自身条件合理选择

-
-
    -
  1. 包车:灵活度高,价格贵(±¥1500/天)
  2. -
  3. 拼车:同车人数少,但可能气场不合
  4. -
  5. 自驾:路况复杂且不能高反,要求极高
  6. -
  7. 跟团:12 人左右,靠窗好位置靠抢
  8. -
  9. 大巴:只去成熟景区和各大购物店
  10. -
-

参考

-
    -
  1. Mars
  2. -
  3. 盛世中华
  4. -
  5. 86元打卡成都必吃小吃
  6. -
  7. 成都·VLOG·下
  8. -
  9. 绝对旅行指南 - 成都篇
  10. -
  11. 绝对旅行指南 - 西藏篇
  12. -
  13. 跨越山海看珠峰
  14. -
-]]>
- - Memory - Travel - - - 成都 - 拉萨 - -
- - 忆·黄山 - /2018/05/01/travel-hs/ - -

黄山归来不看岳

- - -

五岳未归,先品黄山。以前看黄山还是小学课本《黄山》一文介绍黄山的美,黄山的秀丽,黄山的与众不同,这次是亲身去体验黄山的姿态;趁着五一,趁着年轻,趁着…。废话不多讲,先看黄山日出美景

- - -
-

别问我为啥抖,没有支撑点,全程手持…逃

-
-

这次黄山之行并没有做任何功课,计划到实施前后不超过15天,抱着走一步,看一点的心态去玩,没想到五一节假日,来黄山的人不是很多。

-

出行方式

-

杭州 城西客运站 做大巴直达黄山景区,票价:¥110,时间:大约4小时左右到达

-

攻略

-

逃,没有…
-由于到达黄山游客集散中心已是14:00,由于距离黄山还有10多公里,你可以走路去黄山山脚下,而且16:00之后没有大巴去黄山景区。因此随便找了个地吃完中午饭,就往乘大巴车黄山景区去了(¥12/人),由于上山的入口有好几个,我们也没有去研究,大巴到 云谷寺 景区,我们也就下车从这里出发往山上去了,你可以坐缆车去往山顶,我们一行三人,选择了徒步上山,对了门票:¥230/人

-

一路说说笑笑,也没有预订上山的旅店,我们心真大,刚走了没多久,就看到了两个人被交椅抬着下山了,其中一个应该是摔了,头破血流的样子,还没开始,就…;没多管,一路还是很轻松,毕竟都是年轻人,体力不错,走到 白鹅岭 已经开始下雨,雨越下越大,因为在边走边看的路上,我们决定来黄山当然是去 迎客松 的景点,然后我们顺着 白鹅岭 前往 白鹅山庄旅游商场 去避雨,然后是人多的无法挪开脚,此时天色已晚,我们稍作休息,找了半天也没有能睡得地,那床都是人挤人。我们找了个茶馆,吃了些带着的食品,喝了一小时茶,大约20:00左右,我们决定,今晚夜行到 迎客松

-

雨后起了大雾,山顶那时雾色正浓,能见度大约在3米。我们三人也紧随其形,在 光明顶 片区玩了一会,这里看日出不错,当我们并没有这里等日出,毕竟这里离 迎客松 有一小时多的行程,我们要明天早早的在 迎客松 那里拍照装逼,拍完照然后回走去最高峰 莲花峰 ,然而到了 迎客松 才发现,并不像电视上看到的,是在山的悬崖边。好了,这会才23:00多,怎么办,还有好几小时,又没有帐篷什么地可住,三人就在这 迎客松 前的广场上,发现了超大遮阳伞两把,哈哈哈,我们就用遮阳伞前后堵住,加上自己的雨伞,构建了一个堡垒,这下,我们三可用在里面睡觉了,雨后的山上很潮湿,就这样半将半究的,坚持到4点多。

-

天快要亮了,要找地儿去拍日出,我答应别人了,要发日出照片给她,往回走去 莲花峰 那里并不合适,更重要的是山路也被封,不上上去,只好找到 玉屏索道 的另一条路上,这里刚刚好可用看到日出

-

日出

-

拍完日出,我们快速折回到 迎客松 ,那里已经开始有三三两两的人了,我们动作要快,否则等会从索道上来大批人马,嗯,快速装逼完成,迅速撤离战场

-

迎客松

-]]>
- - Memory - Travel - - - 黄山 - -
- - 行·张家界 - /2018/05/20/travel-zjj/ -
    -
  • 时间:2018.06.16——2018.06.19
  • -
  • 地点:杭州——张家界
  • -
  • 目标:武陵源景区,天门山景区,大峡谷景区
  • -
- -

听说张家界是人间仙境,鬼斧神工,嗯,今年端午就去一探究竟,慌慌张张,匆匆忙忙做一份旅行攻略,翻遍百度,爬烂谷歌,都没有找到匹配的攻略,哎,可能是我姿势不对?!

-

张家界,张家界景区共分为四块:张家界国家森林公园杨家界自然保护区天子山自然保护区索溪峪自然保护区四大景区,统称为武陵源风景名胜。

- -

最受欢迎 的四大景区

-
    -
  1. 武陵源景区(森林公园、金鞭溪、袁家界、杨家界、天子山、十里画廊等)
  2. -
  3. 天门山景区(亚州最长的索道、世界公路奇观、玻璃栈道等)
  4. -
  5. 大峡谷风景区(新开发的玻璃桥)
  6. -
  7. 凤凰古城
  8. -
-

出行准备

-
    -
  1. 身份证件等相关证件
  2. -
  3. 数码产品,雨具等
  4. -
  5. 简单洗漱用品及换洗衣物
  6. -
  7. 现金若干(不必太多)
  8. -
  9. 零食(必备:辣条)
  10. -
-

注意事项

-

由于是自由行的方式,因此提醒以下几点

-
    -
  1. 到达张家界后,拒绝一切 人搭话,避免一些麻烦,给行程带来不愉快
  2. -
  3. 保管好自己的物品
  4. -
  5. 张家界火车站出站后即可到汽车站乘坐大巴去武陵源景区,50分钟左右,10(森林公园)—12元(武陵源)
  6. -
  7. 大峡谷,天门山景区玻璃桥都需要提前5天在网上预定
  8. -
-

出行路线

-

整体路线图

-

路线一:

-
    -
  • Day1(16):天门山景区
  • -
  • Day2.Day3(17-18):武陵源景区
  • -
  • Day4(19):大峡谷风景区
  • -
-

路线二(推荐)

-
    -
  • Day1.Day2(16-17):武陵源景区
  • -
  • Day2.Day3(18):大峡谷风景区
  • -
  • Day4(19):天门山景区
  • -
-

扼要路线图

-

标记说明

-
    -
  1. 张家界火车站
  2. -
  3. 武陵源景区
  4. -
  5. 大峡谷风景区
  6. -
  7. 天门山景区
  8. -
-
-

武陵源景区路线

-

门票:245 元+保险费3 元(3天内多次进出有效,含环保车票价)
-开放时间:8:00-17:00
-Day1:森林公园-金鞭溪-杨家界
-Day2:大观台-天子山-十里画廊-索溪湖-武陵源门票站
-从森林公园进,从武陵源出,不走回头路。需要在 丁香榕 住一宿
-武陵源景区

-

大峡谷风景区路线

-

门票:大峡谷(门票122元)+玻璃桥(门票138元)
-开放时间:08:00-17:00
-Day3:玻璃桥-大峡谷

-

天门山景区路线

-

门票:258.00元(含往返索道、环保车)【旺季】
-开放时间:08:00~16:00
-路线Day4:玻璃栈道-天门山寺-天门洞(坐索道上山顶——走西线——再到天门翻水处坐自动扶梯到天门洞——爬999级阶梯——最终坐环保车返回至市区)
-自备中午餐

-

天门山景区路线图

-

住宿

-

现在还未确定路线,个人推荐路线二;其次,16,17,18号需要住宿,要提前预定旅店

-

美食

-

胡师傅三下锅

-

三下锅,所谓的三下锅其实就是一种很方便的干锅,它是由三种主料做成的,炖着不放汤的火锅,三角坪附近的那个“胡师傅三下锅”味道不错,三下锅50元一份,分量很够吃的,包你吃够吃好!推荐的就是干煸肠子,干煸核桃肉和湘西腊肉三种混在一起炖,吃的同时还可以点一份酸萝卜,又脆又酸。真的是极品哦!(吃过后,发现并没有网上说的这么好吃,就是大烩菜,哈哈哈)

-

等等。。。

-

汇总

-

汇总

-

游记

-

废话不说,武陵源景区不用去,虽说是5A景区,除了山还是山,而且商业气息很重,很多地方都不能步行,需要坐缆车,电梯等交通工具,况且这次去森林公园那边在修路,说是在修高铁,建议直接去 大峡谷风景区天门山景区

-

武陵源景区

-

整个武陵
-武陵源

-

大峡谷风景区

-

大峡谷

-

天门山景区

-]]>
- - Memory - Travel - - - 张家界 - -
- - 藏经阁 - /2018/07/16/treasure/ - -

工欲善其事,必先利其器

- - -

记录汇总一些资源库

- -

快速满足你所需各种资源汇总
-Hi World
-创造师导航
-UI设计师导航
-Devdocs

DevTools

-

集齐宇宙IDE,可以召唤神龙::>_<::
-Jetbrains 全家桶
-Android Studio
-Xcode
-Eclipse
-Visual Studio Code
-Sublime
-PostMan
-Xshell Xftp

必备的一些辅助插件,让你效率翻倍

-

通用

-
    -
  1. Alibaba Java Coding Guidelines:阿里巴巴 Java 代码规范
  2. -
  3. GrepConsole:控制台日志自定义
  4. -
  5. GsonFormat:json 生成对应的实体 bean
  6. -
  7. GenerateAllSetter:对象中所有属性生成 set 方法
  8. -
  9. ignore:文件忽略
  10. -
  11. Lombok:简化实体 bean
  12. -
  13. Markdown Navigator:Markdown 文件支持
  14. -
  15. PlantUML integration:UML 文件支持
  16. -
  17. Translation:语言翻译
  18. -
-

Android

-
    -
  1. Android ButterKnife Zelezny:ButterKnife 相关支持
  2. -
  3. Android Material Design Icon Generator:Material icon 生成
  4. -
  5. Android Parcelable code generator:Parcelable 代码生成
  6. -
  7. Android Postfix Completion:.toast,.log 等快捷写法
  8. -
  9. Android Resource Usage Count:资源使用统计
  10. -
  11. Android Studio Prettify:模板代码生成,比如:findViewById
  12. -
  13. ADB Idea:ADB 相关命令
  14. -
  15. EventBus3 Intellij Plugin:EventBus 相关支持
  16. -
  17. LayoutFormatter:格式化 XML 布局
  18. -
  19. SQLScout:SQL 相关支持
  20. -
-

Java

-
    -
  1. Alibaba Cloud Toolkit:服务部署工具
  2. -
  3. JsonToKotlinClass:json 生成 kotlin 对象
  4. -
  5. MyBatisCodeHelperPro:mybatis 支持及 XML 代码调试
  6. -

常用的代码托管平台,设备可以坏,代码不能丢
-Github
-Gitlab
-Gitee
-Coding

装起逼来,我自己都怕

-

根据文字生成字符画

-
    -
  1. taag
  2. -
  3. ascii
  4. -
-

根据图片生成字符画

-
    -
  1. ConvertPhoto2Char
  2. -
  3. Img2Txt
  4. -
-

代码生成图片

-
    -
  1. Carbon
  2. -
-

云端应用

-
    -
  1. Uzer
  2. -
-

其他

-
    -
  1. emojicopy:表情
  2. -
  3. Processon:在线编辑流程图
  4. -
  5. Pickfrom:一站式工具平台
  6. -

开发者常用的官方网站

-

一线大厂开发者网站
-Google Developer
-Apple Developer
-Microsoft Developer
-Facebook Developer
-Twitter Developer
-Github Developer
-Baidu Developer
-Alibaba Developer
-Tencent Developer

一线大厂团队博客
-IBM 技术社区
-Netflix
-Techie Delight
-Linkedin 技术博客
-Dropbox 技术博客
-Facebook 技术博客
-淘宝中间件团队
-美团技术博客
-360技术博客
-有赞技术团队

手机厂商
-Android Developer
-iOS Developer
-Samsung Developer
-Huawei Developer
-XiaoMi Developer
-HTC Developer
-Flyme Developer
-Oppo Developer
-Vivo Developer
-360 Developer
-Smartisan Developer

应用市场
-Google Play
-App Store
-XiaoMi 应用市场
-Huawei 应用市场
-360 应用市场
-酷安市场
-应用宝

小程序
-PWA
-微信小程序
-支付宝小程序
-快应用

要折腾,来呀~
-LineageOS
-XDA
-Mokee
-MoDaCo
-机锋
-智友
-MiUi
-0pengApps

国内外云厂商
-Google Cloud
-Microsoft Azure
-Amazon Web Services
-Aliyun
-Tencent Cloud
-DiDi Cloud
-MT Cloud
-NetEase Cloud

常用镜像
-Tsinghua
-LUG
-Gradle
-Firfox

]]>
- - Resources - - - DevTool - -
- - 微信小程序之 Vant实战(一) - /2021/02/12/wechat-mini1/ - 过年正好时间比较集中,可以把之前的一个想法付诸实践,之前一直想给老爸做一个类似于账单管理的应用,方便他每天把客户需要物品记录成一个清单进行管理,其中主要包含已下功能点。其一,支持添加任务列表(账单);其二,支持任务列表分享(账单)。以上是我的第一期规划功能规划,话不多说我们就一起来跟着我来完成这个小程序的开发吧,本篇主要讲小程序的初始化相关工作

- -

这是我第一次来开发小程序,虽然之前有开发 Android 客户端的经验,有一定的客户端经验,但是小程序却一直没有去实践,我主要是觉得小程序的使用体验真的很差。但随着现在人们的硬件设备越来越好,并且微信团队在应用底层也做了很多的扩展和优化,现在使用小程序开发轻量级的应用还是很方便且高效

-

环境及选型

-
    -
  1. OS:macOS(11.2.1)
  2. -
  3. IDE:WeChat Devtools(1.05.2102010)
  4. -
  5. Node:v15.5.0
  6. -
  7. Vant:1.6.7
  8. -
  9. 微信小程序账号
  10. -
-
-

关于账号的申请,这里不做讲解,请自行解决

-
-

初始化项目

-

创建项目

-

不废话,直接看图

-

-

项目结构

-

项目是一个基于云开发的方式,创建完成后会包含云相关的一些操作实例

-
bill
├── cloudfunctions/ # 云函数管理【清空当前文件夹下的内容】
│ │── callback/
│ │── echo/
│ │── login/
│ └── openapi/
├── minprogram/
│ │── components/ # 组件【清空当前文件夹下内容】
│ │── images/ # 图片管理【清空当前文件夹下内容】
│ │── pages/ # 页面管理【除 index 页面,其余都删除】
│ │ │── addFunction/
│ │ │── chooseLib/
│ │ │── databaseGuide/
│ │ │── deployFunctions/
│ │ │── im/
│ │ │── index/
│ │ │ │── index.js
│ │ │ │── index.json
│ │ │ │── index.wxml
│ │ │ │── index.wxss
│ │ │ └── user-unlogin.png
│ │ │── openapi/
│ │ │── storageConsole/
│ │ └── userConsole/
│ │── style/ # 样式管理
│ │── app.js # 项目入口逻辑管理
│ │── app.json # 组件库配置
│ │── app.wxss # 全局样式设置
│ └── sitemap.json #
├── project.config.json # 项目配置文件
└── README.md # 项目说明
-

精简项目

-

从上面我们可知初始化的项目,包含了一些示例,我们对其精简

-
    -
  1. 清空 cloudfunctions 目录下的云函数内容
  2. -
  3. 根据项目结构里的备注,进行删除相关的文件 -
      -
    • 清空 components 文件夹下的组件
    • -
    • 清空 images 文件夹中的内容
    • -
    • 删除 pages 文件夹下, index 的文件夹
    • -
    • 删除 index 文件夹下的 user-unlogin.png 文件
    • -
    -
  4. -
  5. 修改文件内容 -
      -
    • 清空 index 文件夹下 index.wxml,index.wxss 文件中的内容
    • -
    • 修改 index 文件夹下 index.js 文件内容
    • -
    • 清空 minprogram 文件夹下 app.wxss 文件中的样式内容
    • -
    -
  6. -
  7. 修改配置 -
      -
    • 移除 app.json 文件中 已经移除掉的 pages 的配置
    • -
    • 修改 project.config.json 文件,移除 miniprogram 的配置
    • -
    -
  8. -
-

添加 vant 组件

-

整个步骤,如下截图
-

-
-

执行命令根据官方提供的方式和自身喜好选择,我这里使用的是 yarn 命令进行安装相关的依赖

-
-

测验效果

-

这里以添加 Button 为例来查看是否生效

-
    -
  1. 在 pages/index 路径下的 index.json 文件中,添加 vant 的 Button 组件
    {
    "usingComponents": {
    "van-button": "@vant/weapp/button/index"
    }
    }
    -
  2. -
  3. 在 pages/index 路径下的 index.wxml 文件中,添加 vant 的相关组件
    <van-button type="primary">主要按钮</van-button>
    <van-button type="info">信息按钮</van-button>
    <van-button type="warning">警告按钮</van-button>
    <van-button type="danger">危险按钮</van-button>
    -
  4. -
  5. 编译,在模拟器中查看效果
  6. -
-
-

对于引用的组件,是公共的,可以写在 app.json 文件中

-
-

参考

-
    -
  1. 官方开发文档
  2. -
  3. 微信小程序组件库Vant weapp的使用与weui零基础入门课程
  4. -
-]]>
- - Wechat - - - Wechat - Vant - -
- - Windows 之 常用应用安装 - /2019/09/25/windows-devtool/ - 这是一篇在Windows系统下,持续更新常用开发软件安装汇总,当然一些简单得安装就在这里记录,不废话了

-

JDK

-

官方下载地址,选择需要的版本下载安装包

-

安装完成,设置环境变量,右击我的电脑–>属性–>高级系统设置–>高级–>环境变量

- -
    -
  1. 在系统变量里新建 JAVA_HOME 变量,变量值为你的JDK的安装路径,比如:C:\Program Files\Java\jdk1.8.0_60
  2. -
  3. 在系统变量里新建 CLASSPATH 变量
    .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
    -
  4. -
  5. 找到 path 变量(已存在不用新建)添加变量值
    %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin
    -
  6. -
  7. 保存修改,并启动 CMD 进行验证,输出安装的 Java 版本表示安装成功
    java -version
    -
  8. -
-
-

变量值之间用 ; 隔开。注意原来Path的变量值末尾有没有 ; 号,如果没有,先输入 ; 号再输入

-
-

Git

-
    -
  • 官方下载地址
  • -
  • 清华镜像下载地址
  • -
-

关于 Git 的安装没有什么可说的(使用默认配置即可),基本上就是下一步,下一步,到完成

-

MySQL

-
    -
  • 官方下载地址
  • -
  • 下载MySQL Community Server或者MySQL Installer for Windows都可以,这里我下载的是MySQL Community Server
    -
  • -
-

安装步骤请看截图所示
-windows-mysql-install

-

Tomcat

-

官方网站,选择需要的版本下载

-
    -
  • 官方 Tomcat 镜像:https://downloads.apache.org/tomcat/
  • -
  • 北京理工大学:https://mirrors.bit.edu.cn/apache/tomcat/
  • -
  • 北京外国语大学:https://mirrors.bfsu.edu.cn/apache/tomcat/
  • -
  • 清华大学:https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/
  • -
-

安装

-

下载对应版本的文件,这里比如 apache-tomcat-9.0.26 版本文件,放入你系统某个位置,最好是英文路径且路径中没有空格或特殊字符,并解压文件

-

配置

-

Tomcat 和 JDK 一样为了方便使用都需要配置环境变量,右击我的电脑–>属性–>高级系统设置–>高级–>环境变量

-
    -
  1. 在系统变量里新建 CATALINA_BASE 变量,变量值为你的Tomcat路径,比如:D:\DevTools\apache-tomcat-9.0.26
  2. -
  3. 在系统变量里新建 CATALINA_HOME 变量,变量值为你的Tomcat路径,比如:D:\DevTools\apache-tomcat-9.0.26
  4. -
  5. 找到 path 变量(已存在不用新建)添加变量值
    ;%CATALINA_HOME%\lib;%CATALINA_HOME%\bin;
    -
    -

    变量值之间用 ; 隔开。注意原来Path的变量值末尾有没有 ; 号,如果没有,先输入 ; 号再输入

    -
    -
  6. -
  7. 保存修改,并启动 CMD 进行验证
    statrup
    -
  8. -
-

乱码

-

Tomcat 控制台中中文乱码,需要修改 Tomcat 配置文件 logging.properties 中的字符编码,将默认的 UTF-8 改为 GBK,文件路径<You Tomcat>\conf
-windows-tomcat-encode

-

缓存

-

文件路径<You Tomcat>\conf,修改 context.xml 文件,在 <Context> 标签中添加如下配置即可

-
<Resources cachingAllowed="true" cacheMaxSize="100000" />
-

Maven

-

官方网站

-
    -
  • 官方 Maven 镜像:https://downloads.apache.org/maven/
  • -
  • 北京理工大学:https://mirrors.bit.edu.cn/apache/maven/
  • -
  • 北京外国语大学:https://mirrors.bfsu.edu.cn/apache/maven/
  • -
  • 清华大学:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/
  • -
-

安装

-

下载对应版本的文件,这里比如 apache-maven-3.6.3 版本文件,放入你系统某个位置,最好是英文路径且路径中没有空格或特殊字符,并解压文件

-

配置

-

Maven 和 JDK 一样为了方便使用都需要配置环境变量,右击我的电脑–>属性–>高级系统设置–>高级–>环境变量

-
    -
  1. 在系统变量里新建 MAVEN_HOME 变量,变量值为你的Tomcat路径,比如:D:\DevTools\apache-maven-3.6.3
  2. -
  3. 找到 path 变量(已存在不用新建)添加变量值
    ;%MAVEN_HOME%\bin;
    -
    -

    变量值之间用 ; 隔开。注意原来Path的变量值末尾有没有 ; 号,如果没有,先输入 ; 号再输入

    -
    -
  4. -
  5. 保存修改,并启动 CMD 进行验证
    mvn -version
    -
  6. -
-

镜像地址(可选)

-

如果你服务所依赖的包都是使用公司内部的私服,或者需要加快依赖的同步速度,那么建议你修改 maven 同步镜像的配置,编辑 <You maven>\conf 路径下 settings.xml 文件,在 标签下修改镜像地址

-
<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云central仓库</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云jcenter-public仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云google仓库</name>
<url>https://maven.aliyun.com/repository/google</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云gradle-plugin仓库</name>
<url>https://maven.aliyun.com/repository/gradle-plugin</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云spring仓库</name>
<url>https://maven.aliyun.com/repository/spring</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云spring-plugin插件仓库</name>
<url>https://maven.aliyun.com/repository/spring-plugin</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云grails-core插件仓库</name>
<url>https://maven.aliyun.com/repository/grails-core</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云apache-snapshots仓库</name>
<url>https://maven.aliyun.com/repository/apache-snapshots</url>
</mirror>
</mirrors>
-

本地依赖存放地址

-

默认,maven 的依赖都存放在 C:Users/<Your name>/.m2/reposittory 路径下,你可以指定 maven 同步的依赖存默认放在你设置的路径下,编辑 <You maven>\conf 路径下 settings.xml 文件,修改 标签的内容

-
<localRepository>D:DevTools/maven/repository</localRepository>
-

配置修改验证

-

配置完成,在命令行输入 mvn help:system 测试,查看下载链接里面是否是现在配置的镜像地址,以及下载后的文件是否存放在自定设置的目标地址

-

CMD

-

常用命令

-
    -
  1. IP地址
    ipconfig
    -
  2. -
  3. 查看端口
    # 查看端口号:netstat -ano | findstr 端口号
    netstat -ano | findstr 8080
    # 查看占用端口号进程 :tasklist | findstr 进程号
    tasklist | findstr 12836
    # kill 指定进程:taskkill -PID 进程号 -F
    taskkill -PID 12836 -F
    -截图如下:
    -windows-task
  4. -
-]]>
- - Windows - - - DevTool - -
- - Zxing(一)二维码基础知识 - /2019/05/01/zxing1/ - 移动端开发,一个避不开的老生常谈功能开发,二维码扫描识别(主要)及二维码生成(辅助),虽然已有现成的开源项目提供了功能,仅仅作为功能的开发集成和调试,其实远远不够,应该在完成功能开发的基础上去学习背后的技术点和原理,让我们更加完整的掌握该技术。废话不多说,本篇是 Zxing 相关技术的第一篇文章,本篇不会涉及到应用相关,仅仅是二维码基础知识的学习记录。

- -

QRcode

-

QRcode(全称:Quick Response Code,快速响应矩阵图码)

-
    -
  • 1994 年由日本 DENSO WAVE公司发明,
  • -
  • QR 码使用四种标准化编码模式(数字,字母数字,字节(二进制)和汉字)来存储数据
  • -
  • QR 码可以存储更多信息,可在小空间内打印,可以从 360°任一方向读取,可以对变脏和破损的图码有一定的容错能力,并且可以有效处理各种数据,支持数据合并等
  • -
  • QR 码的种类:QR 码(模型1,模型2)Micro QR 码iQR 码SQRCFrame QR
  • -
-
-

QR 码(模型1,模型2):
-模型1:最早制作的 QR 码。最高版本为 14(73x73 码元),最多可以处理 1167 位数字
-模型2:是模型1的改良版,最高版本为 40(177x177 码元),最多可以处理 7089 位数字,现在我们通常所说的 QR 码一般指模型2
-Micro QR 码:该码只有 1 个定位图案,可以在更小的空间内打印,最高版本为 M4(17x17 码元),最多可以因 35 位数字
-iQR码:可生成正方形或长方形,可以支持内外翻转,黑白反色,圆点图案(直接打标在部件上)。理论上的最高版本为 61(422x422 码元),最多大约可以处理 4万位数字
-SQRC:安全快速响应代码(Secure Quick Response code,简写SQRC)是一种QR代码,在终结符之后包含“私有数据”段而不是指定的填充字节“ec 11”。必须使用加密密钥对此专用数据段进行解密。这可用于存储私人信息和管理公司的内部信息。
-Frame QR:FrameQR是具有“画布区域”的QR码,可以灵活使用。在这个代码的中心是画布区域,其中可以灵活地安排图形,字母等,使得可以布置代码而不会丢失插图,照片等的设计

-
-

标准及发展

-
    -
  • 1997年10月:AIM(自动识别和流动协会)国际
  • -
  • 1999年1月:JIS X 0510
  • -
  • 2000年6月 -
      -
    • ISO / IEC 18004:2000信息技术 - 自动识别和数据捕获技术 - 条形码符号 - QR码(现已撤销)
    • -
    • 定义QR码模型1和2符号
    • -
    -
  • -
  • 2006年9月1日: -
      -
    • ISO / IEC 18004:2006信息技术 - 自动识别和数据捕获技术 - QR码2005条形码符号规范(现已撤销)
    • -
    • 定义QR码2005符号,QR码模型2的扩展。不指定如何读取QR码模型1符号,或要求符合性。
    • -
    -
  • -
  • 2015年2月1日: -
      -
    • ISO / IEC 18004:2015信息 - 自动识别和数据捕获技术 - QR码条形码符号规范
    • -
    • 将QR Code 2005符号重命名为QR Code,并对某些程序和次要更正添加说明
    • -
    -
  • -
-

结构

-

qrcode-structure

-
-

图片来自维基百科,Version7

-
-

如图所示,QR码由 5 部分组成

-
    -
  1. 版本信息:记录具体的版本信息(仅存在 Version7 以上) -
      -
    • version1 是 21x21 的矩阵
    • -
    • 最高 version40 是 177x177 的矩阵
    • -
    • 计算公式:(V-1)*4+21,V 代表版本号
    • -
    -
  2. -
  3. 格式信息:记录使用的掩码纠错等级
  4. -
  5. 数据及容错密钥
  6. -
  7. 数据需求模块
  8. -
  9. 静态区域
  10. -
-

IEC 18004

-

qr-iec-18004
-IEC 18004标准中给出了详细的说明

-
    -
  • 位置探测图形、位置探测图形分隔符、定位图形:用于对二维码的定位,对每个QR码来说,位置都是固定存在的,只是大小规格会有所差异;
  • -
  • 校正图形:规格确定,校正图形的数量和位置也就确定了;
  • -
  • 格式信息:表示改二维码的纠错级别,分为L、M、Q、H;
  • -
  • 版本信息:即二维码的规格,QR码符号共有40种规格的矩阵(一般为黑白色),从21x21(版本1),到177x177(版本40),每一版本符号比前一版本 每边增加4个模块。
  • -
  • 数据和纠错码字:实际保存的二维码信息,和纠错码字(用于修正二维码损坏带来的错误)
  • -
-

掩码

-

掩码的作用

-
    -
  1. 为了对数据区域进行掩模以利于扫描仪识别,可以避免数据区域出现连续的空白或连续的黑色区,
  2. -
  3. 避免了数据区出现类似定位点样式的正方形出现。掩模图案在整个数据区域的网格内不断重复进行掩模计算(功能图形不进行掩模),数据区上对应掩模黑色模块的单元将会反转。
  4. -
  5. 每个二维码上会有两组相同的格式信息出现,并且带有 BCH 纠错
  6. -
-
-

在计算机科学中,掩码就是一个二进制串,通过和数据进行异或运算来变换数据。在 QR 码中,掩码也是通过异或运算来变换数据矩阵,所以 QR 码掩码就是预先定义好的矩阵

-
-

纠错等级

-

相对而言,容错率愈高,QR 码图形面积愈大,所以一般折中使用 15% 容错能力

-

| 错误修正容量 |
-| — | — |
-| L 等级 | 7%的字码可被修正 |
-| M 等级 | 15%的字码可被修正 |
-| Q 等级 | 25%的字码可被修正 |
-| H 等级 | 30%的字码可被修正 |

-

编码 QR 码步骤

-
    -
  1. 数据分析(data analysis):分析输入数据,根据数据决定要使用的 QR 码版本、容错等级和编码模式
  2. -
  3. 编码数据(data encoding):根据选择的编码模式,将输入的字符串转变成比特流,插入模式标识码(mode indicator)和终止标识符(terminator),将比特流切分成 8 比特的字节,加入填充字节来满足标准的数据字码数要求
  4. -
  5. 计算容错码(error correction coding):对步骤二产生的比特流计算容错码,附在比特流之后。高版本的编码方式可能需要将数据流切分成块再分别进行容错码计算
  6. -
  7. 组织数据(structure final message):根据结构图把步骤三得到的有容错的数据切分,准备填充
  8. -
  9. 填充(module placement in matrix):把数据和功能性图样根据标准填充到矩阵中
  10. -
  11. 应用数据掩码(data masking):应用标准中的 8 个数据掩码来变换编码区域的数据,选择最优的掩码应用
  12. -
  13. 填充格式和版本信息(format and version information):计算格式和版本信息填入矩阵,完成 QR 码
  14. -
-

附录

-
    -
  • 维基百科·QR码·中文
  • -
  • 维基百科·QR码·英文·推荐
  • -
  • 二维码(QR code)基本结构及生成原理
  • -
  • QRcode
  • -
  • 二维码的生成细节和原理
  • -
  • 为程序员写的Reed-Solomon码解释
  • -
  • ISO/IEC 18004:2015·PDF
  • -
  • ISO/IEC 18004:2015·PDF
  • -
-]]>
- - Android - - - Zxing - -
- - Zxing(二)Android 模块应用源码探索 - /2019/05/02/zxing2/ - ZXing(“Zebra Crossing”)用于Java,Android的条形码扫描库。虽然当前开源库仅处于维护模式,意味着更改是由贡献的补丁来驱动,只会考虑错误修复和次要的增强功能

-

本篇开启 ZXing项目Android 模块的探索学习之路,那么首先我们要集成该模块到项目中

- -

模块集成

-
    -
  • 下载官方项目Zxing
  • -
  • 使用 AS 创建一个新的 Project
  • -
-
-

编译环境

-
-
    -
  • Android studio:3.4
  • -
  • gradle:5.1.1
  • -
  • SDK:28
  • -
  • JDK:1.8
  • -
-

演示项目rc-android-zxing

-

导入步骤

-
    -
  • 导入 module
    -import_module
  • -
  • 选择 module
    -select_import_module
  • -
  • 移除最小及目标版本设置
    -remove_min_target_version
  • -
  • 添加项目核心依赖
    -import_dependencies
    -com.google.zxing:android-core:3.3.0:实质是android-core模块
    -com.google.zxing:core:3.3.3:实质是core模块
  • -
  • 删除appmodule(可选)
  • -
-

项目展示

-

zxing

-

当然你也可以下载官方提供的应用google play

-

异常问题处理

-

相机出现问题

-
表现
-

如果你运行的设备是 Android 6.0 以上版本,那么在启动应用程序后,应该会提示你“很遗憾,Android 相机出现问题,你可能需要重启设备”,如下图
-project_problem

-
分析
-

分析运行日志,进行定位CaptureActivity.java类,在初始化相机时,由于没有相机权限,因此无法正常运行应用
-zxing_error_log

-
解决方式
-
// CaptureActivity.jaca
// line 266 && line 443
mHolder = surfaceHolder;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (ContextCompat.checkSelfPermission(CaptureActivity.this,
android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// 先判断有没有权限 ,没有就在这里进行权限的申请
ActivityCompat.requestPermissions(CaptureActivity.this,
new String[]{Manifest.permission.CAMERA}, CAMERA_OK);
} else {
// 说明已经获取到摄像头权限了
initCamera(surfaceHolder);
}
} else {
initCamera(surfaceHolder);
}

// line 803
@Override
public void onRequestPermissionsResult(int requestCode
, @NonNull String[] permissions, @NonNull int[] grantResults) {
// If request is cancelled, the result arrays are empty.
if (requestCode == CAMERA_OK) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
initCamera(mHolder);
}
}
}
-

其他问题

-

同样我们在EncodeActivity.java文件中加入读取内存卡的权限READ_EXTERNAL_STORAGE

-

项目分析

-

项目概要

-
rc-android-zxing
├── book/
├── camera/
├── clipboard/
├── encode/
├── history/
├── result/
├── share/
├── wifi/
├── AmbientLightManager
├── BeepManager
├── CaptureActivity
├── CaptureActivityHandler
├── Contents
├── DecodeFormatManager
├── DecodeHandler
├── DecodeHintManager
├── DecodeThread
├── FinishListener
├── HelpHelper
├── InactivityTimer
├── Intents
├── IntentSource
├── LocaleManager
├── PreferencesActivity
├── PreferencesFragment
├── ScanFromWebPageManager
├── ViewfinderResultPointCallback
└── ViewfinderView

-

项目源码

-

总结

-]]>
- - Android - - - Zxing - -
- diff --git a/CNAME b/source/CNAME similarity index 100% rename from CNAME rename to source/CNAME diff --git a/source/README.md b/source/README.md new file mode 100644 index 000000000..119c63701 --- /dev/null +++ b/source/README.md @@ -0,0 +1,21 @@ +

+ BladeCode +

+ +![BladeCode](https://travis-ci.com/BladeCode/BladeCode.github.io.svg?branch=dev) + +## 附录 + +* [Conventional Commits](https://www.conventionalcommits.org) +* [优雅的提交你的 Git Commit Message](https://juejin.im/post/5afc5242f265da0b7f44bee4) +* [Commit message 和 Change log 编写指南](http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html) +* [gitemoji](https://gitmoji.carloscuesta.me) + +## Thanks + +1. [Node](https://nodejs.org) +2. [Hexo](https://hexo.io) +3. [Next](https://theme-next.js.org) +4. [LeanCloud](https://leancloud.cn) +5. [Utterances](https://utteranc.es) +6. [Chinese-copywriting-guidelines](https://github.com/sparanoid/chinese-copywriting-guidelines) diff --git a/source/_data/languages.yml b/source/_data/languages.yml new file mode 100644 index 000000000..5e54de203 --- /dev/null +++ b/source/_data/languages.yml @@ -0,0 +1,11 @@ +zh-CN: + menu: + books: 阅读 + movies: 电影 + links: 友链 + music: Music + app: App + backend: Backend + mobile: Mobile + links_settings: + title: 友链 \ No newline at end of file diff --git a/source/_posts/android-audio-base.md b/source/_posts/android-audio-base.md new file mode 100644 index 000000000..d935db031 --- /dev/null +++ b/source/_posts/android-audio-base.md @@ -0,0 +1,160 @@ +--- +title: Android 音频基础知识 +date: 2018-10-26 10:14:20 +categories: Android +tag: [media] +--- + +关于音频技术是一门庞大且很专业的学术,这里不会阐述该知识的底层原理知识(比如:声音的原理,音波的正弦平面波合成等等),主要介绍音频相关的一些基本的知识概念,以及在实际开发过程中需要掌握关键API等。 + +## 声音 + +"声音是振动产生的`声波`,通过`介质`(`气体`,`固体`,`液体`)传播并能被人或动物`听觉器官`所感知的`波动`现象"。声音的频率一般以[赫兹](https://zh.wikipedia.org/wiki/%E8%B5%AB%E5%85%B9)表示,记为`Hz`,指每秒周期性震动的次数 + + + +![trasound_range_diagram](https://res.cloudinary.com/incoder/image/upload/v1541400788/blog/trasound_range_diagram.png) +>图片来自[Wikipedia](https://zh.wikipedia.org/wiki/%E5%A3%B0%E9%9F%B3) + +* 红:次声波(由火山爆发、龙卷风、雷暴、台风等许多灾害性事件发生前都会产生出次声波,人们就可以利用这种前兆来预报灾害事件的发生) +* 蓝:可听声波(20~20000Hz) +* 绿:超声波(广泛应用于工业、军事、医疗等行业。在工业上,常用超声波来清洗精密零件,原理是利用超声波在清洗液中产生震荡波,使清洗液产生瞬间的小气泡,从而冲洗零件的每个角落) + +## 音频开发应用场景 + +* 音频播放器,录音机 +* 语音电话 +* 音视频监控 +* 音视频直播 +* 音视频编辑/处理软件 +* 蓝牙耳机/音响等 + +## 音频开发具体内容 + +* [音频采集/播放](https://www.incoder.org/2018/10/27/android-audio/) +* 音频算法处理(去噪,静音检测,回声消除,音效处理,功放/增强,混音/分离,等等) +* [音频的编解码和格式转换](https://www.incoder.org/2018/11/07/android-audio-convert/) +* 音频传输协议的开发([SIP](https://zh.wikipedia.org/wiki/%E4%BC%9A%E8%AF%9D%E5%8F%91%E8%B5%B7%E5%8D%8F%E8%AE%AE),[A2DP](https://zh.wikipedia.org/wiki/%E8%97%8D%E7%89%99%E8%A6%8F%E7%AF%84#%E8%97%8D%E7%89%99%E7%AB%8B%E9%AB%94%E8%81%B2%E9%9F%B3%E8%A8%8A%E5%82%B3%E8%BC%B8%E8%A6%8F%E7%AF%84%EF%BC%88A2DP%EF%BC%89),[AVRCP](https://zh.wikipedia.org/wiki/%E8%97%8D%E7%89%99%E8%A6%8F%E7%AF%84#%E9%9F%B3%E9%A2%91%EF%BC%8F%E8%A7%86%E9%A2%91%E8%BF%9C%E7%A8%8B%E6%8E%A7%E5%88%B6%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%EF%BC%88AVRCP%EF%BC%89),等等) + * SIP(Session Initiation Protocol:会话发起协议):一个由IETF MMUSIC工作组开发的协议,作为标准被提议用于建立,修改和终止包括视频,语音,即时通信,在线游戏和虚拟现实等多种多媒体元素在内的交互式用户会话 + * A2DP(Advance Audio Distribution Profile:蓝牙立体声音频传输规范):规定了使用蓝牙异步传输信道方式,传输高质量音乐文件数据的协议堆栈软件和使用方法,基于该协议就能通过以蓝牙方式传输高质量的立体声音乐 + * AVRCP(Audio Video Remote Control Profile:音频/视频远程控制配置文件):用于提供控制 TV、Hi-Fi 设备等的标准接口。此配置文件用于许可单个远程控制设备。 + +## 音频基础知识 + +声音经过麦克风采集后,得到是模拟信号,接着我们需要用程序将采集得到模拟型号,进行转换得到数字信号,这样我们才可以存储,交换等 + +>关于声音信息得到模拟信号的转换,我们一般是无需关心,设备的麦克风这些都已经帮我们转换好了,我们需要关心的是从麦克风得到的模拟信号,如何去转换为数字信号,最终保存为音频文件 + +### 模拟信号转数字信号 + +模拟信号一般通过[PCM(Pulse-code modulation:脉冲编码调制)](https://zh.wikipedia.org/wiki/%E8%84%88%E8%A1%9D%E7%B7%A8%E7%A2%BC%E8%AA%BF%E8%AE%8A)方法转换为数字信号 + +#### 转换步骤 + +1. [采样](https://zh.wikipedia.org/wiki/%E5%8F%96%E6%A8%A3):将一段时间内的连续信号转为离散信号 + * [模拟信号](https://zh.wikipedia.org/wiki/%E8%BF%9E%E7%BB%AD%E4%BF%A1%E5%8F%B7)本身是一种连续信号,它在一定的时间范围内可以有无限多个不同的取值 + * [数值信号](https://zh.wikipedia.org/wiki/%E7%A6%BB%E6%95%A3%E4%BF%A1%E5%8F%B7)指在取值上是离散的,不连续的信号 +2. 量化:值采样得到后的数据,我们用多少位的二进制数字来表示声音的振幅 +3. [编码](https://zh.wikipedia.org/wiki/%E8%AA%9E%E9%9F%B3%E7%B7%A8%E7%A2%BC):将采样量化后的数据按照一定的格式进行记录 + +#### PCM + +音频编码最多只能做到无限接近,至少目前的技术只能这样,相对自然界的信号,任何数字音频编码方式都是有损,因为无法完全还原。在计算机应用中,能够达到最高保真的就是PCM编码,因此PCM约定俗成了无损编码(PCM代表了数字音频中最佳的保真水平,并不意味着PCM就能够确保信号绝对保真,PCM也只能做到最大程度的无限接近) + +经过采集和量化后的声音信号已经是数字形式了,但是为了便于计算机的存储,处理,传输,还必须按照一定的要求进行数据`压缩`和`编码` + +##### 压缩 + +一种音频文件格式可以支持多种编码,例如AVI文件格式,但多数的音频文件仅支持一种音频编码 + +主要的音频文件格式: + +* 无损格式,例如:[WAV](https://zh.wikipedia.org/wiki/WAV),[FLAC](https://zh.wikipedia.org/wiki/FLAC),[APE](https://zh.wikipedia.org/wiki/Monkey%27s_Audio),[ALAC](https://zh.wikipedia.org/wiki/Apple_Lossless),[WavPack(WV)](https://zh.wikipedia.org/wiki/WavPack) +* 有损格式,例如:[MP3](https://zh.wikipedia.org/wiki/MP3),[AAC](https://zh.wikipedia.org/wiki/%E9%80%B2%E9%9A%8E%E9%9F%B3%E8%A8%8A%E7%B7%A8%E7%A2%BC),[Ogg Vorbis](https://zh.wikipedia.org/wiki/Vorbis),[Opus](https://zh.wikipedia.org/wiki/Opus_%28%E9%9F%B3%E9%A2%91%E6%A0%BC%E5%BC%8F%29) + +##### 编码 + +根据编码方式的不同,音频编码技术分为三种 + +* 波形编码:音质质量高,编码速率也很高。脉冲编码调变(PCM)、自适应增量调制( ADM )、Adaptive( ADPCM )等都属于该类编码器。 +* 参数编码:音质质量低,编码速率也很低 +* 混合编码:音质和速率介于波形编码,参数编码之间 + +>为什么音频需要编码 +1. PCM所量化得到的数据是原始无损的数据,文件很大,不利于传播,存储等 +2. 如果都是未压缩的文件,那么基本无法做到差异化即部分需要知识产权保护的组织或机构等 + +## 音频开发中重要参数 + +### [采样率(samplerate)](https://zh.wikipedia.org/wiki/%E9%87%87%E6%A0%B7%E7%8E%87) + +指每秒从连续信号中提取并组成离散信号的采样个数,也就是1S内,对模拟信号进行多少次采样;采样频率越高,说明采样点之间越密集,记录这段音频所用的数据量就越大,因此音质也就越好 + +>[为什么通用的采样率是44.1kHz?](https://www.zhihu.com/question/22027722) + +### 量化精度(位宽) + +用二进位来表示每一个采样值,也称为量化位数,声音信号的量化位数一般是4,6,8,12或16 bits. + +这个数值的数据类型大小可以是:4bit,8bit,16bit,32bit等等,位数越多,表示的就越精细,声音的质量也就越好,当然文件大小也会成倍增大 + +### 声道数(channels) + +由于音频的采集和播放是可以叠加的,因此,可以同时从多个音频源采集声音,并分别输出到不同的扬声器,故声道数一般表示声音录制时的音源数量或回放时相应的扬声器数量 + +* 单声道(Mono):1 +* 双声道(Stereo):2 + +### 比特率 + +比特率是音频文件每秒占据的字节数(比特数) + +比特率规定适用“比特每秒”(`bit/s`或`bps`)为单位,其中`ps`指的是`/s`,即每秒。 + +通常我们在音乐播放软件中看到的音乐质量『标准(128kbit/s),较高(198kbit/s),极高(320kbit/s)』表述的即比特率 + +### 音频帧(frame) + +视频每一帧就是一张图像,而音频数据是流式,本身没有明确的一帧帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗称2.5ms~60ms为单位的数据量为一帧音频。 + +### 理论音频的大小 + +假设某通道的音频信号是采样率为8kHz,位宽为16bit,20ms一帧,双通道,则一帧音频数据的大小为: +```java +# 一帧音频的大小 +int size = 8000 x 16bit x 0.02s x 2 = 5120 bit = 640 byte; +``` + +## 音频处理开源项目 + +### VoIP相关 + +基于IP的语音传输(英语:Voice over Internet Protocol,缩写为VoIP)是一种语音通话技术,经由网际协议(IP)来达成语音通话与多媒体会议,也就是经由互联网来进行通信。其他非正式的名称有IP电话(IP telephony)、互联网电话(Internet telephony)、宽带电话(broadband telephony)以及宽带电话服务(broadband phone service)。 +* imsdroid +* sipdroid +* csipsimple +* linphone +* WebRTC + +### 算法相关 + +* ffmpeg +* speex + +### 其他 + +MP3编码库 +* [Lame](https://sourceforge.net/projects/lame) + +## Android提供相关API + +* 音频采集:MediaRecoder,AudioRecord +* 音频播放:SoundPool,MediaPlayer,AudioTrack +* 音频编解码:MediaCodec +* NDK API:OpenSL ES + +## 附录 + +* [语音编码](https://zh.wikipedia.org/wiki/%E8%AA%9E%E9%9F%B3%E7%B7%A8%E7%A2%BC) +* [高级音频编码 ● AAC](https://zh.wikipedia.org/wiki/%E9%80%B2%E9%9A%8E%E9%9F%B3%E8%A8%8A%E7%B7%A8%E7%A2%BC) +* [音频技术可以延展众多应用场景](https://yq.aliyun.com/articles/628109) \ No newline at end of file diff --git a/source/_posts/android-audio.md b/source/_posts/android-audio.md new file mode 100644 index 000000000..02618abdf --- /dev/null +++ b/source/_posts/android-audio.md @@ -0,0 +1,401 @@ +--- +title: Android 音频录制与播放 +date: 2018-10-27 09:44:46 +categories: Android +tag: [media] +--- + +上一篇主要介绍了音频相关的一些基础知识,本篇主要介绍在Android系统中如何进行音频的录制,播放 + +## 音频录制 + +Android SDK中提供了`AudioRecord`,`MediaRecorder`两个API经行音频的录制,具体的优缺点等如下: + +* [AudioRecord](https://developer.android.google.cn/reference/android/media/AudioRecord) 『added in API level 3』(基于字节流录音): + 优点:可以实现语音的实时处理,进行边录边播,对音频的实时处理。 + 缺点:输出的是PCM的语音数据,如果保存成音频文件是不能被播放器播放的。要用到这个去进行处理。 + 适用场景:需要实时处理分析的录音场景等,如:会说话的汤姆猫『[AppStore](https://itunes.apple.com/cn/app/%E4%BC%9A%E8%AF%B4%E8%AF%9D%E7%9A%84%E6%B1%A4%E5%A7%86%E7%8C%AB/id377194688?mt=8) | [GooglePlay](https://play.google.com/store/apps/details?id=com.outfit7.talkingtom&hl=zh)』 + + + +* [MediaRecorder](https://developer.android.google.cn/reference/android/media/MediaRecorder) 『added in API level 1』(基于文件音视频录制): + 优点:封装度很高,操作简单,无需处理中间录制过程;录制的音频文件是经过压缩的,需要设置编码器;录制的音频文件可以使用系统自带的播放器播放 + 缺点:无法实现实时处理音频,输出的音频格式少。 + 适用场景:录制过程需要实时处理的场景等 + +## 音频播放 + +* [AudioTrack](https://developer.android.google.cn/reference/android/media/AudioTrack)『added in API level 3』: +AudioTrack 则更接近底层,提供了非常强大的控制能力,支持低延迟播放,适合流媒体和VoIP语音电话等场景 + +* [SoundPool](https://developer.android.google.cn/reference/android/media/SoundPool) 『added in API level 1』: + 优点:主要用于播放一些较短的声音片段,支持从程序的资源或文件系统加载;CPU的资源占用量低、反应延迟小,并且可以加载多个音频到`SoundPool`中,通过资源ID来管理 + 缺点:SoundPool加载资源,最大只能申请 **1MB** 的内存控件,因此只能用来播放一些很短的声音片段 + 适用场景:播放短,反应要求高的音频 + +* [MediaPlayer](https://developer.android.google.cn/reference/android/media/MediaPlayer) 『added in API level 1』(基于字节流音视频播放): + 优点:支持本地,网络音频资源的播放 + 缺点:资源占用量较高、加载延迟时间较长;不支持多个音频同时播放等 + 适用场景:播放长音频 + +>Google官方给出了[兼容支持](https://developer.android.google.cn/guide/topics/media/media-formats#audio-formats) + +## AudioRecord + +### 录制流程 + +1. 构造一个`AudioRecord`对象,其中需要的最小音频缓存`buffer`大小可以通过`getMinBufferSize()`方法得到,如果`buffer`容量过小,将导致对象构造失败 +2. 初始化一个`buffer`,该`buffer` 大于等于`AudioRecord`对象用于写音频数据的`buffer`大小 +3. 开始录音 +4. 创建一个数据流,一边从`AudioRecord`中读取音频数据到初始化的`buffer`,一边将`buffer`中的数据导入数据流 +5. 关闭数据流 +6. 停止录音 + +### 参数配置 + +* audioSource :音频采集的输入源 + * DEFAULT(默认) + * VOICE_RECOGNITION(用于语音识别,等同于DEFAULT) + * MIC(由手机麦克风输入) + * VOICE_COMMUNICATION(用于VoIP应用) +* sampleRateInHz:采样率 + 目前44100Hz是唯一可以保证兼容所有Android手机的采样率 +* channelConfig:通道数的配置 + * CHANNEL_IN_MONO:单通道 + * CHANNEL_IN_STEREO:双通道 +* audioFormat:数据位宽 + * ENCODING_PCM_8BIT:8bit + * ENCODING_PCM_16BIT:16bit +* bufferSizeInBytes:AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小 + +### 示例代码 + +```java +public class AudioCapturer { + + private static final String TAG = "AudioCapturer"; + + private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC; + private static final int DEFAULT_SAMPLE_RATE = 44100; + private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; + private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + + private AudioRecord mAudioRecord; + private int mMinBufferSize = 0; + + private Thread mCaptureThread; + private boolean mIsCaptureStarted = false; + private volatile boolean mIsLoopExit = false; + + private OnAudioFrameCapturedListener mAudioFrameCapturedListener; + + public interface OnAudioFrameCapturedListener { + public void onAudioFrameCaptured(byte[] audioData); + } + + public boolean isCaptureStarted() { + return mIsCaptureStarted; + } + + public void setOnAudioFrameCapturedListener(OnAudioFrameCapturedListener listener) { + mAudioFrameCapturedListener = listener; + } + + public boolean startCapture() { + return startCapture(DEFAULT_SOURCE, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG, + DEFAULT_AUDIO_FORMAT); + } + + public boolean startCapture(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) { + + if (mIsCaptureStarted) { + Log.e(TAG, "Capture already started !"); + return false; + } + + mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat); + if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) { + Log.e(TAG, "Invalid parameter !"); + return false; + } + Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !"); + + mAudioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize); + if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) { + Log.e(TAG, "AudioRecord initialize fail !"); + return false; + } + + mAudioRecord.startRecording(); + + mIsLoopExit = false; + mCaptureThread = new Thread(new AudioCaptureRunnable()); + mCaptureThread.start(); + + mIsCaptureStarted = true; + + Log.d(TAG, "Start audio capture success !"); + + return true; + } + + public void stopCapture() { + + if (!mIsCaptureStarted) { + return; + } + + mIsLoopExit = true; + try { + mCaptureThread.interrupt(); + mCaptureThread.join(1000); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + + if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { + mAudioRecord.stop(); + } + + mAudioRecord.release(); + + mIsCaptureStarted = false; + mAudioFrameCapturedListener = null; + + Log.d(TAG, "Stop audio capture success !"); + } + + private class AudioCaptureRunnable implements Runnable { + + @Override + public void run() { + + while (!mIsLoopExit) { + + byte[] buffer = new byte[mMinBufferSize]; + + int ret = mAudioRecord.read(buffer, 0, mMinBufferSize); + if (ret == AudioRecord.ERROR_INVALID_OPERATION) { + Log.e(TAG , "Error ERROR_INVALID_OPERATION"); + } + else if (ret == AudioRecord.ERROR_BAD_VALUE) { + Log.e(TAG , "Error ERROR_BAD_VALUE"); + } + else { + if (mAudioFrameCapturedListener != null) { + mAudioFrameCapturedListener.onAudioFrameCaptured(buffer); + } + Log.d(TAG , "OK, Captured "+ret+" bytes !"); + } + } + } + } +} +``` + +## AudioTrack + +### 播放流程 + +1. 配置参数,初始化内部的音频播放缓冲区到,如果`buffer`容量过小,将导致对象构造失败 +2. 开始播放 +3. 需要一个线程,不断地向 AudioTrack 的缓冲区`写入`音频数据,注意,这个过程一定要及时,否则就会出现`underrun`的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“送入”音频数据,导致内部的音频播放缓冲区为空 +4. 停止播放,释放资源 + +### 参数配置 + +* streamType:当前应用使用的哪一种音频管理策略 +当系统有多个进程需要播放音频时,这个管理策略会决定最终的展现效果 + * STREAM_VOCIE_CALL:电话声音 + * STREAM_SYSTEM:系统声音 + * STREAM_RING:铃声 + * STREAM_MUSCI:音乐声 + * STREAM_ALARM:警告声 + * STREAM_NOTIFICATION:通知声 +* sampleRateInHz:采样率 +采样率的取值范围必须在 4000Hz~192000Hz 之间 +* channelConfig:通道数的配置 + * CHANNEL_IN_MONO:单通道 + * CHANNEL_IN_STEREO:双通道 +* audioFormat:数据位宽 + * ENCODING_PCM_8BIT:8bit + * ENCODING_PCM_16BIT:16bit +* bufferSizeInBytes:配置的是 AudioTrack 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小 +* mode:AudioTrack 播放模式 + * MODE_STATIC + static:一次性将所有的数据都写入播放缓冲区,简单高效,通常用于播放铃声、系统提醒的音频片段 + * MODE_STREAM + streaming:按照一定的时间间隔不间断地写入音频数据,理论上它可用于任何音频播放的场景 + +### 示例代码 + +```java +public class AudioPlayer { + + private static final String TAG = "AudioPlayer"; + + private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC; + private static final int DEFAULT_SAMPLE_RATE = 44100; + private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; + private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + private static final int DEFAULT_PLAY_MODE = AudioTrack.MODE_STREAM; + + private boolean mIsPlayStarted = false; + private int mMinBufferSize = 0; + private AudioTrack mAudioTrack; + + public boolean startPlayer() { + return startPlayer(DEFAULT_STREAM_TYPE,DEFAULT_SAMPLE_RATE,DEFAULT_CHANNEL_CONFIG,DEFAULT_AUDIO_FORMAT); + } + + public boolean startPlayer(int streamType, int sampleRateInHz, int channelConfig, int audioFormat) { + + if (mIsPlayStarted) { + Log.e(TAG, "Player already started !"); + return false; + } + + mMinBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat); + if (mMinBufferSize == AudioTrack.ERROR_BAD_VALUE) { + Log.e(TAG, "Invalid parameter !"); + return false; + } + Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !"); + + mAudioTrack = new AudioTrack(streamType,sampleRateInHz, + channelConfig,audioFormat,mMinBufferSize,DEFAULT_PLAY_MODE); + + if (mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED) { + Log.e(TAG, "AudioTrack initialize fail !"); + return false; + } + + mIsPlayStarted = true; + + Log.d(TAG, "Start audio player success !"); + + return true; + } + + public int getMinBufferSize() { + return mMinBufferSize; + } + + public void stopPlayer() { + + if (!mIsPlayStarted) { + return; + } + + if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { + mAudioTrack.stop(); + } + + mAudioTrack.release(); + mIsPlayStarted = false; + + Log.d(TAG, "Stop audio player success !"); + } + + public boolean play(byte[] audioData, int offsetInBytes, int sizeInBytes) { + + if (!mIsPlayStarted) { + Log.e(TAG, "Player not started !"); + return false; + } + + if (sizeInBytes < mMinBufferSize) { + Log.e(TAG, "audio data is not enough !"); + return false; + } + + if (mAudioTrack.write(audioData,offsetInBytes,sizeInBytes) != sizeInBytes) { + Log.e(TAG, "Could not write all the samples to the audio device !"); + } + + mAudioTrack.play(); + + Log.d(TAG , "OK, Played "+sizeInBytes+" bytes !"); + + return true; + } +} +``` + +## MediaRecorder + +![mediarecorder](https://developer.android.google.cn/images/mediarecorder_state_diagram.gif) + +如上所示表述整个MediaRecorder的整个生命过程,可以看出初始化之后,在任意的状态下调用`reset()`方法均可以回到MediaRecorder刚刚初始化完成的状态 + +## MediaPlayer + +![mediaplayer](https://developer.android.google.cn/images/mediaplayer_state_diagram.gif) + +### MediaPlayer 工作流程 + +1. 创建一个MediaPlayer对象 +2. 调用setDataSource()方法,设置音频文件的路径 +3. 接着调用prepare()方法,使MediaPlayer进入的准备状态 +4. 调用start()方法,开始播放音频『pause()方法表示:暂停播放』 + +### MediaPlayer常用的控制方法 + +| 方法名 | 功能描述 | +| ---------- | --- | +| setDataSource() | 设置要播放的音频文件的位置 | +| prepare() | 在开始播放之前调用这个方法完成准备工作 | +| start() | 开始或继续播放音频 | +| pause() | 暂停播放音频 | +| reset() | 将MediaPlayer对象重置到刚刚创建的状态 | +| seekTo() | 从指定位置开始播放音频 | +| stop() | 停止播放音频。调用这个方法后的MediaPlayer对象无法再播放音频 | +| release() | 释放掉与MediaPlayer对象相关的资源 | +| isPlaying() | 判断当前MediaPlayer是否正在播放音频 | +| getDuration() | 获取站如的音频文件的时长 | + +### 注意事项 + +1. 在使用`star()`播放流媒体之前,需要装载流媒体资源。这里最好使用`prepareAsync()`异步的方式装载流媒体资源,在使用`prepareAsync()`异步加载时,为避免还没有装载完就调用了`start()`而保存,需要绑定`MediaPlayer.setOnPreparedListener()`事件,它将在异步装在完成后回调 +原因:流媒体资源的装载是会消耗系统资源,在一些硬件不理想的设备上,如果使用`prepare()`同步的方式装载资源,可能会造成UI界面卡顿,其次避免装载超时而引发`ANR`等问题 +2. 使用完MediaPlayer需要回收资源。MediaPlayer时很消耗系统资源的,所以在使用完MediaPlayer,及时主动回收资源 +3. 对于单曲循环之类的操作,除了使用`setLooping()`方法设置之外,还可以为MediaPlayer注册回调函数,`MediaPlayer.setOnCompletionListener()`,它会在MediaPlayer播放完被回调 +4. 由于无法确保播放的流媒体是完整(中间有错误),我们需要处理这个错误,否则会影响用户体验。可以在MediaPlayer中注册`setOnErrorListener()`错误回调,一般重新播放或者播放下一个流媒体 + +## 跨平台 + +关于音频编解码在各平台上的情况如下 +![wiki-ecode](https://res.cloudinary.com/incoder/image/upload/v1541055152/blog/android-audio.png) + +从上图可知,[AAC](https://zh.wikipedia.org/wiki/%E9%80%B2%E9%9A%8E%E9%9F%B3%E8%A8%8A%E7%B7%A8%E7%A2%BC),[FLAC](https://zh.wikipedia.org/wiki/FLAC),[MP3](https://zh.wikipedia.org/wiki/MP3)三种编码是全平台支持的音频编码方式(或音频压缩方式),注意编码方式并不是文件格式即文件的扩展名 + +* AAC 主要扩展名 + * `.aac` + * `.mp4` + * `.m4a` +* FLAC 扩展名 + * `.flac` +* MP3 扩展名 + * `.mp3` + +## 总结 + +* 音频的录制,Android SDK提供了两套音频采集的API,分别是:`MediaRecorder`和`AudioRecord`,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如:`AMR`,`OGG`等)并存储成文件,而后者则更接近底层,能够更加自由灵活的控制,可以得到原始的一帧帧`PCM`音频数据 +* 如果要简单的进行音频的采集,录制成音频文件,则推荐适用`MediaRecorder`,而如果需要对音频做进一步的算法处理,或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议适用`AudioRecord` +* `MediaRecorder`底层的实现也是调用了`AudioRecord`与`Android Framework` 层的`AudioFlinger`进行交互 + +> 关于音视频相关的资料参差不齐,目前尚未有大量相关专门的书籍来介绍该领域的图书或者易懂视频,很多情况需要根据所处应用场景灵活应变。 +推荐刚刚发行的一本关于音频方面的图书[《Android音视频开发》](https://item.jd.com/35027062396.html) +推荐国内比较专业音视频方面相关的介绍[《雷霄骅的专栏》](http://blog.csdn.net/leixiaohua1020) + +## 附录 + +* [音频编码格式的比较](https://zh.wikipedia.org/wiki/%E9%9F%B3%E9%A2%91%E7%BC%96%E7%A0%81%E6%A0%BC%E5%BC%8F%E7%9A%84%E6%AF%94%E8%BE%83) +* [第一行代码](https://book.douban.com/subject/26915433) +* [Android MediaRecorder架构详解](http://www.isclab.org.cn/archives/2014/12/2946.html) +* [参考代码](https://github.com/googlesamples/android-MediaRecorder) +* [浏览器引擎](https://zh.wikipedia.org/wiki/%E6%8E%92%E7%89%88%E5%BC%95%E6%93%8E) +* [主流浏览器内核介绍](https://www.cnblogs.com/zichi/p/5116764.html) +* [腾讯X5内核介绍](https://x5.tencent.com/tbs/product/tbs.html) +* [Android 音视频开发学习思路](http://www.cnblogs.com/renhui/p/7452572.html) \ No newline at end of file diff --git a/source/_posts/android-location1.md b/source/_posts/android-location1.md new file mode 100644 index 000000000..39083a8df --- /dev/null +++ b/source/_posts/android-location1.md @@ -0,0 +1,21 @@ +--- +title: Android 定位知多少(一) +date: 2019-05-26 15:29:30 +categories: Android +tag: [Location] +--- + +手机行业持续不断发展,为我们生活带了很多便利,在我们生活中到处都存在它的痕迹,它不仅是一个工具而且还是有温度的组手,协助你解决生活中的各种问题,渐渐成为了人们不可或缺的“器官”。它为什么就能进化成人类的一部分呢?其中一个重要的功能就是定位,看似单一的功能却渗透了我们各种场景,比如:定位,导航,这种基础的功能,还基于定位社交聊天,运动轨迹画像,出行等等,解决了人与人,人与物,物与物之间在位置上的问题。那么我本节就来聊一聊定位相关的一些知识,以及手机是如何在 Android 系统中是如何进行定位的 + + + +在 Android 系统中,定位主要分为两类: +* 硬件定位(GPS 定位,北斗定位 ……) +* 网络定位 + * 基站定位 + * WiFi 定位 + + +## 参考 + +1. [用户位置](https://developer.android.google.cn/training/location?hl=zh_cn) \ No newline at end of file diff --git a/source/_posts/android-location2.md b/source/_posts/android-location2.md new file mode 100644 index 000000000..06fa5045c --- /dev/null +++ b/source/_posts/android-location2.md @@ -0,0 +1,10 @@ +--- +title: Android 定位知多少(二) +date: 2019-05-26 17:29:30 +categories: Android +tag: [Location] +--- + +本篇主要讲解定位策略 + + \ No newline at end of file diff --git a/source/_posts/android-string.md b/source/_posts/android-string.md new file mode 100644 index 000000000..d3ee4ca81 --- /dev/null +++ b/source/_posts/android-string.md @@ -0,0 +1,142 @@ +--- +title: Android XML字符串 +date: 2019-10-27 09:44:46 +categories: Android +tag: [Util] +--- + +Android在开发过程中,一些特殊字符时无法直接在 `strings.xml` 文件中写,需要用对应的转义字符代替或者在特殊符号(比如:`´`,`"` 等待)前添加 `\` ,比如一个 `TextView` 控件中,需要动态替换其中的一些数据,再比如需要调整 `TextView` 字体的一些HTML样式(比如:粗体,斜体,下划线等),虽然这些都可以用 `TextView` 去修改,但更简单的方法是设置string提供的属性即可 + + + +## 特殊字符 + +```xml + + + + + I'm developer + + I\'m developer + +``` + +## 动态替换或拼接 + +* `%n$ms`:代表输出的是字符串,n代表是第几个参数,设置m的值可以在输出之前放置空格 +* `%n$md`:代表输出的是整数,n代表是第几个参数,设置m的值可以在输出之前放置空格 +* `%n$mf`:代表输出的是浮点数,n代表第几个参数,m在浮点类型之前放置几个空格 + +### XML配置 + +```xml + + + + Hello, %1$s, You have %2$d new messages. total cost %3$4.2f + +``` + +### Java设置 + +```java +mTextConent = (TextView) findViewById(R.id.tv_String); +mTextConent.setText(String.format(getString(R.string.welcome_messages), "Jerry", 36, 195.1255)); +``` + +## HTML标记 + +* `` 表示 **粗体** 文本。 +* `` 表示 *斜体* 文本。 +* `` 表示 下划线 文本。 + +```xml + + + Welcome to Android! + Welcome to Android! + Welcome to Android! + +``` + +## ASCII对照表 + +| ASCII码 | 符号 | ASCII码 | 符号 | ASCII码 | 符号 | ASCII码 | 符号 | +| --------- | -------------------------------- | --------- | ------ | -------- | ---- | -------- | ---- | +| `@` | @ | `:` | : | ` ` | 空格 | ` ` | 空格 | +| `!` | ! | `"` | " | `#` | # | `$` | $ | +| `%` | % | `&` | & | `'` | ´ | `(` | ( | +| `* ` | * | `+` | + | `,` | , | `)` | ) | +| `-` | - | `.` | . | `/` | / | `:` | : | +| `;` | ; | `<` | < | `=` | = | `>` | > | +| `?` | ? | `@` | @ | `[` | [ | `\` | > | +| `]` | ] | `^` | ^ | `_` | _ | ``` | ` | +| `{` | { | `| ` | | | `}` | } | `~` | ~ | +| ` ` | (空格,在xml首字符中不会被忽略) | `¡` | ¡ | `¢` | ¢ | `£` | £ | +| `¤` | ¤ | `¥` | ¥ | `¦` | ¦ | `§` | § | +| `¨` | ¨ | `©` | © | `ª` | ª | `«` | « | +| `¬` | ¬ | `­` | -­ | `®` | ® | `¯` | ¯ | +| `°` | ° | `±` | ± | `²` | ² | `³` | ³ | +| `´` | ´ | `µ` | µ | `¶` | ¶ | `·` | • | +| `¸` | ¸ | `¹` | ¹ | `º` | º | `»` | » | +| `¼` | ¼ | `½` | ½ | `¾` | ¾ | `¿` | ¿ | +| `À` | À | `Á` | Á | `Â` |  | `Ã` | à | +| `Ä` | Ä | `Å` | Å | `Æ` | Æ | `Ç` | Ç | +| `È` | È | `É` | É | `Ê` | Ê | `Ë` | Ë | +| `Ì` | Ì | `Í` | Í | `Î` | Î | `Ï` | Ï | +| `Ð` | Ð | `Ñ` | Ñ | `Ò` | Ò | `Ó` | Ó | +| `Ô` | Ô | `Õ` | Õ | `Ö` | Ö | `×` | × | +| `Ø` | Ø | `Ù` | Ù | `Ú` | Ú | `Û` | Û | +| `Ü` | Ü | `Ý` | Ý | `Þ` | Þ | `ß` | ß | +| `à` | à | `á` | á | `â` | â | `ã` | ã | +| `ä` | ä | `å` | å | `æ` | æ | `ç` | ç | +| `è` | è | `é` | é | `ê` | ê | `ë` | ë | +| `ì` | ì | `í` | í | `î` | î | `ï` | ï | +| `ð` | ð | `ñ` | ñ | `ò` | ò | `ó` | ó | +| `ô` | ô | `õ` | õ | `ö` | ö | `÷` | ÷ | +| `ø` | ø | `ù` | ù | `ú` | ú | `û` | û | +| `ü` | ü | `ý` | ý | `þ` | þ | `ÿ` | ÿ | +| `Ā` | Ā | `ā` | ā | `Ă` | Ă | `ă` | ă | +| `Ą` | Ą | `ą` | ą | `Ć` | Ć | `ć` | ć | +| `Ĉ` | Ĉ | `ĉ` | ĉ | `Ċ` | Ċ | `ċ` | ċ | +| `Č` | Č | `č` | č | `Ď` | Ď | `ď` | ď | +| `Đ` | Đ | `đ` | đ | `Ē` | Ē | `ē` | ē | +| `Ĕ` | Ĕ | `ĕ` | ĕ | `Ė` | Ė | `ė` | ė | +| `Ę` | Ę | `ę` | ę | `Ě` | Ě | `ě` | ě | +| `Ĝ` | Ĝ | `ĝ` | ĝ | `Ğ` | Ğ | `ğ` | ğ | +| `Ġ` | Ġ | `ġ` | ġ | `Ģ` | Ģ | `ģ` | ģ | +| `Ĥ` | Ĥ | `ĥ` | ĥ | `Ħ` | Ħ | `ħ` | ħ | +| `Ĩ` | Ĩ | `ĩ` | ĩ | `Ī` | Ī | `ī` | ī | +| `Ĭ` | Ĭ | `ĭ` | ĭ | `Į` | Į | `į` | į | +| `İ` | İ | `ı` | ı | `IJ` | IJ | `ij` | ij | +| `Ĵ` | Ĵ | `ĵ` | ĵ | `Ķ` | Ķ | `ķ` | ķ | +| `ĸ` | ĸ | `Ĺ` | Ĺ | `ĺ` | ĺ | `Ļ` | Ļ | +| `ļ` | ļ | `Ľ` | Ľ | `ľ` | ľ | `Ŀ` | Ŀ | +| `ŀ` | ŀ | `Ł` | Ł | `ł` | ł | `Ń` | Ń | +| `ń` | ń | `Ņ` | Ņ | `ņ` | ņ | `Ň` | Ň | +| `ň` | ň | `ʼn` | ʼn | `Ŋ` | Ŋ | `ŋ` | ŋ | +| `Ō` | Ō | `ō` | ō | `Ŏ` | Ŏ | `ŏ` | ŏ | +| `Ő` | Ő | `ő` | ő | `Œ` | Œ | `œ` | œ | +| `Ŕ` | Ŕ | `ŕ` | ŕ | `Ŗ` | Ŗ | `ŗ` | ŗ | +| `Ř` | Ř | `ř` | ř | `Ś` | Ś | `ś` | ś | +| `Ŝ` | Ŝ | `ŝ` | ŝ | `Ş` | Ş | `ş` | ş | +| `Š` | Š | `š` | š | `Ţ` | Ţ | `ţ` | ţ | +| `Ť` | Ť | `ť` | ť | `Ŧ` | Ŧ | `ŧ` | ŧ | +| `Ũ` | Ũ | `ũ` | ũ | `Ū` | Ū | `ū` | ū | +| `Ŭ` | Ŭ | `ŭ` | ŭ | `Ů` | Ů | `ů` | ů | +| `Ű` | Ű | `ű` | ű | `Ų` | Ų | `ų` | ų | +| `Ŵ` | Ŵ | `ŵ` | ŵ | `Ŷ` | Ŷ | `ŷ` | ŷ | +| `Ÿ` | Ÿ | `Ź` | Ź | `ź` | ź | `Ż` | Ż | +| `ż` | ż | `Ž` | Ž | `ž` | ž | | | + +## 附录 + +* [字符串资源](https://developer.android.google.cn/guide/topics/resources/string-resource?hl=zh-cn) \ No newline at end of file diff --git a/source/_posts/bigdecimal.md b/source/_posts/bigdecimal.md new file mode 100644 index 000000000..879accb4f --- /dev/null +++ b/source/_posts/bigdecimal.md @@ -0,0 +1,406 @@ +--- +title: BigDecimal +date: 2019-10-20 10:04:00 +categories: Util +tag: [Util] +--- + +float 和 double 同样也是可以表示浮点数,为啥在对于要求精确的进度计算时,尤其是关于币值相关,都采用 BigDecimal 类型来处理? + +1. float 和 double 类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的 **快速近似** 计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该用于要求精确结果的场合。——《Effective Java》 +2. float 精度 7 位,double 精度 16 位 + + + +综上所述,对于精确计算时,就不能采用 float 和 double 来计算了,而是 正确的使用 BigDecimal 时,结果才是精确的,为什么会这么说,那就跟着我一块来深入了解 BigDecimal,我们查看`java.math`路径下,除了 BigDecimal 还有 BigInteger ,因此,我们先去了解 BigInteger + +## BigInteger + +Java 中,由 CPU 原生提供的整形最大范围是 64 位`long`类型整数。使用`long`类型整数可以直接通过 CPU 指令进行计算,速度非常快。如果使用的整数范围超过了`long`类型的范围怎么办?这时就只能用软件来模拟一个大整数。BigInteger表示不可变的任意精度的整数(继承`Number`)。BigInteger 内部用一个 `int[]` 数组来模拟一个非常大的整数 + +### 构造方法 + +* BigInteger(byte[] val):将包含 BigInteger 的二进制补码表示形式的 byte 数组转换为 BigInteger +* BigInteger(int signum, byte[] magnitude):将 BigInteger 的符号-数量表示形式转换为 BigInteger。 +* BigInteger(int bitLength, int certainty, Random rnd):构造一个随机生成的正 BigInteger,它可能是一个具有指定 bitLength 的素数 +* BigInteger(int numBits, Random rnd):构造一个随机生成的 BigInteger,它是在 0 到 (2numBits - 1)(包括)范围内均匀分布的值 +* BigInteger(String val):将 BigInteger 的十进制字符串表示形式转换为 BigInteger,常用构造方法 +* BigInteger(String val, int radix):将指定基数的 BigInteger 的字符串表示形式转换为 BigInteger + +```java +BigInteger bi = new BigInteger("1234567890"); +// 计算出 bi⁵ = 2867971860299718107233761438093672048294900000 +System.out.println(bi.pow(5)); +``` + +### 常用运算方法 + +对于加减乘除等运算,BigInteger 提供了对应的方法 + +```java +BigInteger a = new BigInteger("1234567890"); +BigInteger b = new BigInteger("9876543210"); +// a+b = 11111111100 +System.out.println("a+b = " + a.add(b)); +// a-b = -8641975320 +System.out.println("a-b = " + a.subtract(b)); +// a*b = 12193263111263526900 +System.out.println("a*b = " + a.multiply(b)); +// a/b = 0 +System.out.println("a/b = " + a.divide(b)); +``` + +### 转换 + +和 `long` 类型整数运算比,`BigInteger`不会有范围限制,但缺点是速度比较慢。 + +```java +BigInteger i = new BigInteger("123456789000"); +// 123456789000 +System.out.println(i.longValue()); +// java.lang.ArithmeticException: BigInteger out of long range +System.out.println(i.multiply(i).longValueExact()); +``` + +>使用 `longValueExact()` 方法时,如果超出了 `long` 类型的范围,会抛出 `ArithmeticException` + +`BigInteger` 和 `Integer` 、 `Long` 一样,也是不可变类,并且也继承自 `Number` 类。因为 `Number` 定义了转换为基本类型的几个方法: + +* 转换为`byte`:`byteValue()` +* 转换为`short`:`shortValue()` +* 转换为`int`:`intValue()` +* 转换为`long`:`longValue()` +* 转换为`float`:`floatValue()` +* 转换为`double`:`doubleValue()` + +通过上述方法,可以把 `BigInteger` 转换为基本类型。如果 `BigInteger` 表示的范围超过了基本类型,转换时将丢失高位信息,即结果不一定准确;因此,如果需要 准确的转换成基本类型,可以使用 `intValueExact()`、`longValueExact()` 等方法,在转换时如果超出范围,将直接抛出 `ArithmeticException`异常 + +## BigDecimal + +`BigDecimal` 与 `BigInteger`类似,`BigDecimal` 表示一个任意大小且精度完全准确的浮点数。BigDecimal 是由任意精度的整数非标度值(unscaled value)和 32 位的整数标度(scale)组成,通常用于币值的计算。 + +### 构造方法 + +BigDecimal 拥有16 个构造方法,常用如下三种 + +* BigDecimal BigDecimal(double d); // 不允许使用,精度不能保证 +* BigDecimal BigDecimal(String s); // 常用,推荐使用 +* static BigDecimal valueOf(double d); // 常用,推荐使用 + +```java +BigDecimal bigDecimal = new BigDecimal(2); +BigDecimal bString = new BigDecimal("2.3"); +BigDecimal bDouble = new BigDecimal(2.3); +BigDecimal bDouble1 = BigDecimal.valueOf(2.3); +// 输出:bigDecimal = 2 +System.out.println("bigDecimal = " + bigDecimal); +// 输出:bString = 2.3 +System.out.println("bString = " + bString); +// 输出:bDouble = 2.29999999999999982236431605997495353221893310546875 +System.out.println("bDouble = " + bDouble); +// 输出:bDouble1 = 2.3 +System.out.println("bDouble1 = " + bDouble1); +``` + +* 参数类型为 double 的构造方法的结果有一定的不可预知性; +* 参数类型为 String 的构造方法的结果是完全可预知的,因此我们在编写时尽量都用 String 的构造方法 +* 当 double 必须用作 BigDecimal 的源时可以用 BigDecimal 的静态方法 value() + +### 常用方法 + +```java +BigDecimal a = new BigDecimal("1234567890.56789"); +BigDecimal b = new BigDecimal("9876543210.01234"); +// a+b = 11111111100.58023 +System.out.println("a+b = " + a.add(b)); +// a-b = -8641975319.44445 +System.out.println("a-b = " + a.subtract(b)); +// a*b = 12193263116887551591.2965077626 +System.out.println("a*b = " + a.multiply(b)); +// 报错:ArithmeticException,因为除不尽 +// System.out.println("a/b = " + a.divide(b)); +// 保留10位小数并四舍五入 +System.out.println("a/b = " + a.divide(b, 10, RoundingMode.HALF_UP)); +``` + +### 转换 + +与`BigInteger`相同 + +### 舍入模式 + +* ROUND_CEILING:向 **正无限大方向舍入** 的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.UP;如果结果为负,则舍入行为类似于 RoundingMode.DOWN + ```java + 5.5 => 6 + 1.1 => 2 + -1.0 => -1 + -2.5 => -2 + BigDecimal a1 = new BigDecimal("5.5"); + BigDecimal a2 = new BigDecimal("1.1"); + BigDecimal a3 = new BigDecimal("-1.0"); + BigDecimal a4 = new BigDecimal("-2.5"); + System.out.println("ROUND_CEILING模式:" + a1.setScale(0, RoundingMode.CEILING)); + System.out.println("ROUND_CEILING模式:" + a2.setScale(0, RoundingMode.CEILING)); + System.out.println("ROUND_CEILING模式:" + a3.setScale(0, RoundingMode.CEILING)); + System.out.println("ROUND_CEILING模式:" + a4.setScale(0, RoundingMode.CEILING)); + ``` +* RoundingMode.DOWN:向 **零方向舍入** 的舍入模式。从不对舍弃部分前面的数字加 1(即截尾)。注意,此舍入模式始终不会增加计算值的绝对值 + ```java + 5.5 => 5 + 1.1 => 1 + -1.0 => -1 + -2.5 => -2 + BigDecimal a1 = new BigDecimal("5.5"); + BigDecimal a2 = new BigDecimal("1.1"); + BigDecimal a3 = new BigDecimal("-1.0"); + BigDecimal a4 = new BigDecimal("-2.5"); + System.out.println("DOWN模式:" + a1.setScale(0, RoundingMode.DOWN)); + System.out.println("DOWN模式:" + a2.setScale(0, RoundingMode.DOWN)); + System.out.println("DOWN模式:" + a3.setScale(0, RoundingMode.DOWN)); + System.out.println("DOWN模式:" + a4.setScale(0, RoundingMode.DOWN)); + ``` +* RoundingMode.FLOOR(此舍入模式始终不会增加计算值):向 **负无限大方向舍入** 的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.DOWN;如果结果为负,则舍入行为类似于 RoundingMode.UP + ```java + 5.5 => 5 + 1.1 => 1 + -1.0 => -1 + -2.5 => -3 + BigDecimal a1 = new BigDecimal("5.5"); + BigDecimal a2 = new BigDecimal("1.1"); + BigDecimal a3 = new BigDecimal("-1.0"); + BigDecimal a4 = new BigDecimal("-2.5"); + System.out.println("FLOOR模式:" + a1.setScale(0, RoundingMode.FLOOR)); + System.out.println("FLOOR模式:" + a2.setScale(0, RoundingMode.FLOOR)); + System.out.println("FLOOR模式:" + a3.setScale(0, RoundingMode.FLOOR)); + System.out.println("FLOOR模式:" + a4.setScale(0, RoundingMode.FLOOR)); + ``` +* RoundingMode.HALF_DOWN:向 **最接近数字方向舍入** 的舍入模式,如果与两个相邻数字的距离相等,则向 **下舍入** 。如果被舍弃部分 > 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN + ```java + 5.5 => 5 + 1.1 => 1 + -1.1 => -1 + -2.5 => -2 + BigDecimal a1 = new BigDecimal("5.5"); + BigDecimal a2 = new BigDecimal("1.1"); + BigDecimal a3 = new BigDecimal("-1.0"); + BigDecimal a4 = new BigDecimal("-2.5"); + System.out.println("HALF_DOWN模式:" + a1.setScale(0, RoundingMode.HALF_DOWN)); + System.out.println("HALF_DOWN模式:" + a2.setScale(0, RoundingMode.HALF_DOWN)); + System.out.println("HALF_DOWN模式:" + a3.setScale(0, RoundingMode.HALF_DOWN)); + System.out.println("HALF_DOWN模式:" + a4.setScale(0, RoundingMode.HALF_DOWN)); + ``` +* RoundingMode.HALF_EVEN:向 **最接近数字方向舍入** 的舍入模式,如果与两个相邻数字的距离相等,则向 **相邻的偶数舍入** 。如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;如果为偶数,则舍入行为同 RoundingMode.HALF_DOWN + ```java + 5.5 => 6 + 1.1 => 1 + -1.0 => -1 + -2.5 => -2 + BigDecimal a1 = new BigDecimal("5.5"); + BigDecimal a2 = new BigDecimal("1.1"); + BigDecimal a3 = new BigDecimal("-1.0"); + BigDecimal a4 = new BigDecimal("-2.5"); + System.out.println("HALF_EVEN模式:" + a1.setScale(0, RoundingMode.HALF_EVEN)); + System.out.println("HALF_EVEN模式:" + a2.setScale(0, RoundingMode.HALF_EVEN)); + System.out.println("HALF_EVEN模式:" + a3.setScale(0, RoundingMode.HALF_EVEN)); + System.out.println("HALF_EVEN模式:" + a4.setScale(0, RoundingMode.HALF_EVEN)); + ``` +* RoundingMode.HALF_UP(此舍入模式就是通常学校里讲的四舍五入):向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。如果被舍弃部分 >= 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN + ```java + 5.5 => 6 + 1.1 => 1 + -1.1 => -1 + -2.5 => -3 + BigDecimal a1 = new BigDecimal("5.5"); + BigDecimal a2 = new BigDecimal("1.1"); + BigDecimal a3 = new BigDecimal("-1.1"); + BigDecimal a4 = new BigDecimal("-2.5"); + System.out.println("HALF_UP模式:" + a1.setScale(0, RoundingMode.HALF_UP)); + System.out.println("HALF_UP模式:" + a2.setScale(0, RoundingMode.HALF_UP)); + System.out.println("HALF_UP模式:" + a3.setScale(0, RoundingMode.HALF_UP)); + System.out.println("HALF_UP模式:" + a4.setScale(0, RoundingMode.HALF_UP)); + ``` +* RoundingMode.UNNECESSARY:用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。如果对生成精确结果的操作指定此舍入模式,则抛出 ArithmeticException + ```java + 1.5 => 抛出 ArithmeticException + 1.1 => 抛出 ArithmeticException + 1.0 => 1 + -1.1 =>抛出 ArithmeticException + -1.6 => 抛出 ArithmeticException + ``` +* RoundingMode.UP:**远离零方向舍入** 的舍入模式。始终对非零舍弃部分前面的数字加 1。注意,此舍入模式始终不会减少计算值的绝对值 + ```java + 5.5 => 6 + 1.1 => 2 + -1.0 => -1 + -2.5 => -3 + BigDecimal a1 = new BigDecimal("5.5"); + BigDecimal a2 = new BigDecimal("1.1"); + BigDecimal a3 = new BigDecimal("-1.0"); + BigDecimal a4 = new BigDecimal("-2.5"); + System.out.println("UP模式:" + a1.setScale(0, RoundingMode.UP)); + System.out.println("UP模式:" + a2.setScale(0, RoundingMode.UP)); + System.out.println("UP模式:" + a3.setScale(0, RoundingMode.UP)); + System.out.println("UP模式:" + a4.setScale(0, RoundingMode.UP)); + ``` + +### 格式化 + +DecimalFormat 解析 +| 符号 | 位置 | 描叙 | +|-----------|----------|--------------------------------------------------------------------------------------------------------------------------| +| 0 | 数字 | 阿拉伯数字,如果不存在则显示0 | +| # | 数字 | 阿拉伯数字,如果不存在不显示0 | +| . | 数字 | 小数分隔符或货币小数分隔符 | +| , | 数字 | 分组分隔符 | +| E | 数字 | 分隔科学计数法中的尾数和指数。在前缀或后缀中无需加引号 | +| - | 数字 | 负号 | +| ; | 子模式边界 | 分隔正数和负数子模式 | +| % | 子模式边界 | 乘以 100 并显示为百分数 | +| \u2030 | 子模式边界 | 乘以 1000 并显示为千分数 | +| ¤(\u00A4) | 子模式边界 | 货币记号,由货币符号替换。如果两个同时出现,则用国际货币符号替换。如果出现在某个模式中,则使用货币小数分隔符,而不使用小数分隔符 | +| ' | 子模式边界 | 用于在前缀或或后缀中为特殊字符加引号,例如 "'#'#"将 123 格式化为 "#123"。要创建单引号本身,请连续使用两个单引号:"# o''clock" | + +```java +// 建立货币格式化引用 +NumberFormat currency = NumberFormat.getCurrencyInstance(); +// 建立百分比格式化引用 +NumberFormat percent = NumberFormat.getPercentInstance(); +// 百分比小数点最多3位 +percent.setMaximumFractionDigits(3); +BigDecimal loanAmount = new BigDecimal("150.48"); +BigDecimal interestRate = new BigDecimal("0.008"); +BigDecimal interest = loanAmount.multiply(interestRate); +// 贷款金额: ¥150.48 +System.out.println("贷款金额:\t" + currency.format(loanAmount)); +// 利率: 0.8% +System.out.println("利率:\t" + percent.format(interestRate)); +// 利息: ¥1.20 +System.out.println("利息:\t" + currency.format(interest)); + +//=============================================================== +DecimalFormat df = new DecimalFormat(); +// 格式化之前的数字 +double data = 1234.56789; + +// 1.定义要显示的数字的格式(这种方式会四舍五入) +String style = "0.0"; +df.applyPattern(style); +// 1-->1234.6 +System.out.println("1-->" + df.format(data)); + +// 2.在格式后添加诸如单位等字符 +style = "00000.000 kg"; +df.applyPattern(style); +// 2-->01234.568 kg +System.out.println("2-->" + df.format(data)); + +// 3.模式中的"#"表示如果该位存在字符,则显示字符,如果不存在,则不显示。 +style = "##000.000 kg"; +df.applyPattern(style); +// 3-->1234.568 kg +System.out.println("3-->" + df.format(data)); + +// 4.模式中的"-"表示输出为负数,要放在最前面 +style = "-000.000"; +df.applyPattern(style); +// 4-->-1234.568 +System.out.println("4-->" + df.format(data)); + +// 5.模式中的","在数字中添加逗号,方便读数字 +style = "-0,000.0#"; +df.applyPattern(style); +// 5-->-1,234.57 +System.out.println("5-->" + df.format(data)); + +// 6.模式中的"E"表示输出为指数,"E"之前的字符串是底数的格式, +// "E"之后的是字符串是指数的格式 +style = "0.00E000"; +df.applyPattern(style); +// 6-->1.23E003 +System.out.println("6-->" + df.format(data)); + +// 7.模式中的"%"表示乘以100并显示为百分数,要放在最后。 +style = "0.00%"; +df.applyPattern(style); +// 7-->123456.79% +System.out.println("7-->" + df.format(data)); + +// 8.模式中的"\u2030"表示乘以1000并显示为千分数,要放在最后。 +style = "0.00\u2030"; +// 在构造函数中设置数字格式 +DecimalFormat df1 = new DecimalFormat(style); +// df.applyPattern(style); +// 8-->1234567.89‰ +System.out.println("8-->" + df1.format(data)); +``` + +### 其他 + +#### 科学计数法问题 + +```java +BigDecimal b = new BigDecimal("0.0000001"); +// 输出结果:1E-7 +System.out.println(b.toString()); +// 输出结果:0.0000001 +System.out.println(b.toPlainString()); +``` + +>当 BigDecimal的值 小于一定值时(测试时发现:小于等于0.0000001)时,则会被记为科学计数法。可以使用 toPlainString()方法显示原来的值 + +#### 去除无效的 0 + +```java +BigDecimal b = new BigDecimal("0.000000100000000"); +// 1E-7 +System.out.println(b.stripTrailingZeros().toString()); +// 0.0000001 +System.out.println(b.stripTrailingZeros().toPlainString()); +``` + +>stripTrailingZeros() 方法的本质是去除掉多余的0,其返回数据类型是BigDecimal,同样的在使用时需要注意科学技术法的问题 + +#### 保留小数位 + +```java +double num = 13.154215; +// 方式一 +DecimalFormat df1 = new DecimalFormat("0.00"); +String str = df1.format(num); +// 13.15 +System.out.println(str); +// 方式二 +// #.00 表示两位小数 #.0000四位小数 +DecimalFormat df2 =new DecimalFormat("#.00"); +String str2 =df2.format(num); +// 13.15 +System.out.println(str2); +// 方式三 +// %.2f %. 表示 小数点前任意位数 2 表示两位小数 格式后的结果为f 表示浮点型 +String result = String.format("%.2f", num); +// 13.15 +System.out.println(result); +``` + +#### 大小比较 + +在比较两个BigDecimal的值是否相等时,要特别注意,使用 `equals()` 方法不但要求两个BigDecimal的 **值相等** ,还要求它们的 **scale()相等**,因此如果只是比较数值的大小,必须使用 `compareTo()` 方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于 + +```java +BigDecimal d1 = new BigDecimal("123.456"); +BigDecimal d2 = new BigDecimal("123.45600"); +// false,因为scale不同 +System.out.println(d1.equals(d2)); +// true,因为d2去除尾部0后scale变为2 +System.out.println(d1.equals(d2.stripTrailingZeros())); +// 0 +System.out.println(d1.compareTo(d2)); +``` + +## 附录 + +* [Java中浮点类型的精度问题 double float](https://www.cnblogs.com/baiqiantao/p/7449176.html) +* [廖雪峰 BigInteger](https://www.liaoxuefeng.com/wiki/1252599548343744/1279767986831393) +* [廖雪峰 BigDecimal](https://www.liaoxuefeng.com/wiki/1252599548343744/1279768011997217) +* [BigDecimal](https://doc.yonyoucloud.com/doc/jdk6-api-zh/java/math/BigDecimal.html) +* [Java 9中新的货币API](http://it.deepinmind.com/java/2015/01/06/looking-into-the-java-9-money-and-currency-api.html) \ No newline at end of file diff --git a/source/_posts/charles.md b/source/_posts/charles.md new file mode 100644 index 000000000..2d029a549 --- /dev/null +++ b/source/_posts/charles.md @@ -0,0 +1,168 @@ +--- +title: Charles 使用教程 +date: 2018-11-29 11:25:46 +categories: DevTool +tag: [Charles] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1559463393/blog/charles.png) + +Charles is an HTTP proxy / HTTP monitor / Reverse Proxy that enables a developer to view all of the HTTP and SSL / HTTPS traffic between their machine and the Internet. This includes requests, responses and the HTTP headers (which contain the cookies and caching information) + + + +Charles是一个HTTP代理/ HTTP监视器/ 反向代理,使开发人员能够查看其机器和Internet之间的所有HTTP和SSL / HTTPS流量,这包括请求,响应和HTTP标头(包含cookie和缓存信息) + +## Charles + +主要特点 +* [SSL代理](https://www.charlesproxy.com/documentation/proxying/ssl-proxying/) - 以纯文本格式查看SSL请求和响应 +* [Bandwidth Throttling](https://www.charlesproxy.com/documentation/proxying/throttling/)模拟较慢的Internet连接,包括延迟 +* AJAX调试 - 以树或文本形式查看XML和JSON请求和响应 +* [AMF](https://www.charlesproxy.com/documentation/additional/amf/) - 以树形式查看Flash Remoting / Flex Remoting消息的内容 +* 重复请求以测试后端更改 +* 编辑测试不同输入的请求 +* 用于拦截和编辑请求或响应的断点 +* 使用W3C验证器验证记录的HTML,CSS和RSS / atom响应 + +>本篇文章操作均基于Charlers 4.2.8版本,及 macOS 10.14.5 版本 + +### 安装 + +* Windows:略 +* macOS:略 +>下载地址:[官方Charles](https://www.charlesproxy.com/download/) + +#### 激活 + +有能力,请支持付费支持正版~ +有能力,请支持付费支持正版~ +有能力,请支持付费支持正版~ + +仅供个人学习研究和交流使用,请勿用于任何商业用途。 +Charles ——> Help ——> Register Charles... +``` +Registered Name: https://zhile.io +License Key: 48891cf209c6d32bf4 +``` +[激活密钥来自知了](https://zhile.io/2017/07/07/charles-proxy-usage-and-license.html) + +### 配置 + +配置流程 +1. 获取操作系统网络IP地址 +2. 修改客户端网络IP连接 +3. 在操作系统及客户端上安装证书 +4. 设置SSL代理 +> 以上配置要求,操作系统(Windows,macOS)及客户端(Android,iOS)连接在 **同一WiFi网络** + +#### 获取系统 IP + +不管是是 Windows 系统还是 macOS 系统都可以通过 Charles 来获取,获取方式 `Help` ——> `Local IP Address` +![charles-ip](https://res.cloudinary.com/incoder/image/upload/v1559436487/blog/charles-ip.png) + +##### Windows + +使用命令行查看网络 IP 地址 `ipconfig` +![charles-windows-ip](https://res.cloudinary.com/incoder/image/upload/v1559437724/blog/charles-windows-ip.png) + +##### macOS + +* 使用命令行查看网络 IP 地址 `ifconfig en0` + ![charles-mac-ip](https://res.cloudinary.com/incoder/image/upload/v1559436484/blog/charles-mac-ip.png) +* macOS系统设置查看IP 地址 `System Preferences` ——> `Network` + ![charles-network-ip](https://res.cloudinary.com/incoder/image/upload/v1559437653/blog/charles-network-ip.png) + +#### 查看监听端口 + +`Proxy`——> `Proxy settings...` +![charles-view-port](https://res.cloudinary.com/incoder/image/upload/v1559437270/blog/charles-view-port.png) + +#### 客户端设置 + +要求手机网络与 PC 网络同链接在一个路由器网络下,这样手机的请求都将通过 PC,因此在 Charles 上可以看到手机上的网络请求。 + +手机上安装下面的步骤请看下面的详细介绍,安装完证书,Charles 将会收到提示,进行允许即可 +![charles-allow](https://res.cloudinary.com/incoder/image/upload/v1559465134/blog/charles-allow.png) + +##### Android + +* 修改网络配置选项 + +* 导入Charles证书,使用浏览器打开 [www.charlesproxy.com/getssl](http://www.charlesproxy.com/getssl) 或 [http://chls.pro/ssl](chls.pro/ssl),下载证书,并进行安装 + +##### iOS + +* 修改网络配置选项 + +* 导入Charles证书 + +* 证书授权 + +##### 模拟器 + +#### Charles设置 + +* Install Charles Root Certificate +完成客户端的设置,我们此时再对 Charles 进行设置,首先我们先进性安装 Charles 证书,`Help` ——> `SSL Proxying...` ——> `Install Charles Root Certificate` + ![charles-install-system](https://res.cloudinary.com/incoder/image/upload/v1559464456/blog/charles-install-system.png) +* 添加证书 + ![charles-add](https://res.cloudinary.com/incoder/image/upload/v1559464726/blog/charles-add.png) +* 证书授权设置 + ![charles-ca-settings](https://res.cloudinary.com/incoder/image/upload/v1559463067/blog/charles-ca-settings.png) +* SSL Proxy settings + ![charles-ssl-settings](https://res.cloudinary.com/incoder/image/upload/v1559463076/blog/charles-ssl-settings.png) + * Host:为需要过滤的域名地址,`*` 表示不过滤 + * Port:固定为443,`*` 表示任意端口 + +## 抓包 + +不废话,请看图 +![charles-overview](https://res.cloudinary.com/incoder/image/upload/v1559467975/blog/charles-overview.png) + +* Structure:视图将网络请求按访问的域名分类 +* Sequence:视图将网络请求按访问的时间排序 + +> 客户端请不要开启其他代理 + +## 进阶 + +### 过滤网络请求 + +* 方法一:在上面抓包的截图中,已经讲过,适用于 **临时型** 对请求进行过滤 +* 方法二:`Proxy` ——> `Recording settings` ——> `include` ,适用于 **经常性** 请求过滤 + ![charles-filter-often](https://res.cloudinary.com/incoder/image/upload/v1559470804/blog/charles-filter-often.png) + +### Map 功能 + +#### 设置本地映射 + +指的是将网络请求重定向到本地的文件,适用于开发过程中,把线上的静态资源映射到本地,这样可以方便调试并及时查看效果,确定无误后再发布到线上环境 +![charles-map-local](https://res.cloudinary.com/incoder/image/upload/v1559473299/blog/charles-map-local.png) + +#### 设置远程映射 + +指的是将网络请求重定向到另一个网络请求地址,适用于开发过程中,需要将请求重定向到其他的服务上 +![charles-map-remote](https://res.cloudinary.com/incoder/image/upload/v1559473845/blog/charles-map-remote.png) + +### 重复发送网络请求 + +可以更加需要重复一次或多次的请求,对于多次的请求可用于服务器的压力测试 +![charles-repeat](https://res.cloudinary.com/incoder/image/upload/v1559475397/blog/charles-repeat.png) + +## 常见问题 + +1. Charles无法抓取到客户端网络请求 + * 查看你的客户端网络设置,是否正确 + * 查看你的客户端和 Charles 是否是处于同一网络环境 +2. Charles 无法抓取 Https 网络请求 + * 查看你的客户端和 Charles 是否安装证书,并设置终是允许 +3. 网络请求及网络响应信息中文乱码 +4. 如果需要抓本地应用(即拦截电脑上的应用接口),请确保你未开启翻墙代理软件 + +## 附录 + +* [Charles Document](https://www.charlesproxy.com/documentation) +* [Charles抓包的安装,使用说明以及常见问题解决](https://blog.csdn.net/zhangxiang_1102/article/details/77855548) +* [抓包工具Charles的使用心得](https://www.jianshu.com/p/fdd7c681929c) +* [Charles抓包https](https://www.jianshu.com/p/ec0a38d9a8cf) \ No newline at end of file diff --git a/source/_posts/cloud-gcp.md b/source/_posts/cloud-gcp.md new file mode 100644 index 000000000..cca93ca8e --- /dev/null +++ b/source/_posts/cloud-gcp.md @@ -0,0 +1,305 @@ +--- +title: Google Cloud Platform for VPN +date: 2018-11-07 14:43:46 +categories: Google +tag: [vpn] +--- + +![cloud-gcp](https://res.cloudinary.com/incoder/image/upload/v1541559310/blog/cloud-gcp.png) + +随着云产品的普及推广,各路国际大场也是纷纷推出了相关云产品的试用,其中具有代表性的[Google Cloud](https://cloud.google.com),[Amazon](https://aws.amazon.com),本篇主要讲解Googel Cloud产品的试用,并搭建SSR服务 + + + +Google Cloud 特点 + +* 可使用所有Cloud Platform产品 +* 免费获得$300赠金 +* 免费使用结束后不会自动收费 + +## 准备 + +* Google Email +* visa 信用卡(需要$1进行认证,认传完成后返还$1) +> 因为Google本身在大陆是无法正常访问的,因此需要先**自备梯子**,可以先使用[Lantern](https://github.com/getlantern/lantern) + +## GCP + +### 申请Google Cloud Platform + +[官网申请](https://cloud.google.com/free):https://cloud.google.com/free + +![gcp-register1](https://res.cloudinary.com/incoder/image/upload/v1542540776/blog/gcp-register1.png) +* 国家地区:`中国` +* 服务条款:`同意` +* 动态邮件:可选,根据自身需要勾选 + +![gcp-register2](https://res.cloudinary.com/incoder/image/upload/v1542540935/blog/gcp-register2.png) + +根据需要填写一些信息,由于我的Google账号已是开发者账号,一些信息都是完善的,所以Google直接关联了信息,因此也不会再扣除$1,如果你是新账号,详细步骤可参考附录 + +## VM创建 + +在创建VM之前,我们先进行[网络防火墙修改](https://console.cloud.google.com/networking/firewalls/list),避免后续的麻烦 + +![gcp-firewall-settings](https://res.cloudinary.com/incoder/image/upload/v1574522003/blog/gcp-firewall-settings.png) +![gcp-create-firewall](https://res.cloudinary.com/incoder/image/upload/v1542545777/blog/gcp-create-firewall.png) +规则设置如下: +![gcp-firewall-rule](https://res.cloudinary.com/incoder/image/upload/v1542546500/blog/gcp-firewall-rule.png) + +* 名称:自己命名一个用于区分其它得规则 +* 来源IP地址范围:`0.0.0.0/0`,这个不要写错 +其它按照图上设置即可 + +### 创建VM实例 + +![gcp-create-vm](https://res.cloudinary.com/incoder/image/upload/v1542546431/blog/gcp-create-vm.png) +![gcp-create-vm-init](https://res.cloudinary.com/incoder/image/upload/v1542546640/blog/gcp-create-vm-init.png) +![gcp-create-vm-course](https://res.cloudinary.com/incoder/image/upload/v1542546774/blog/gcp-create-vm-course.png) +* 名称:自己写一个即可 +* 地区:建议选亚洲,别人推荐`asia-east1-c`,**台湾彰化县**实测延迟低,我这里选择了香港 +* 机器类型:选微型(1个共享vCPU) +* 启动磁盘:推荐CentOS 7,当然也可以其它,选择自己熟悉的系统即可 +其中关于网络的设置如下: +![gcp-create-vm-network](https://res.cloudinary.com/incoder/image/upload/v1542547144/blog/gcp-create-vm-network.png) +* 名称:任意输入即可(小写字母开头,不能为大写字母) + +设置完成后,创建VM实例 + +## 连接VM + +当然,你可以使用浏览器打开连接VM +![gcp-link-vm-chrome](https://res.cloudinary.com/incoder/image/upload/v1542547502/blog/gcp-link-vm-chrome.png) + +经过实际操作,你会发现,在浏览器中操作延迟很高,因此我们就采用其它客户端去连接刚刚创建的这台服务器,下面分别以 [Xshell(Windows)](https://www.netsarang.com/products/xsh_overview.html) 和 [iTerm(macOS)](https://www.iterm2.com)来演示如何与 GCP 建立连接 + +### 使用Xshell + +#### 密钥生成 + +1. 新建用户密钥生成向导 +![gcp-link-vm-xshell1](https://res.cloudinary.com/incoder/image/upload/v1542547944/blog/gcp-link-vm-xshell1.png) +2. 密钥类型长度设置 +![gcp-link-vm-xshell2](https://res.cloudinary.com/incoder/image/upload/v1542548236/blog/gcp-link-vm-xshell2.png) +3. 生成密钥 +![gcp-link-vm-xshell3](https://res.cloudinary.com/incoder/image/upload/v1542548733/blog/gcp-link-vm-xshell3.png) +4. 设置密钥名称及密码 +![gcp-link-vm-xshell4](https://res.cloudinary.com/incoder/image/upload/v1542548796/blog/gcp-link-vm-xshell4.png) +5. 保存密钥 +![gcp-link-vm-xshell5](https://res.cloudinary.com/incoder/image/upload/v1542548832/blog/gcp-link-vm-xshell5.png) + +#### GCP添加密钥 + +* 元数据 +![gcp-link-vm-settings](https://res.cloudinary.com/incoder/image/upload/v1542549326/blog/gcp-link-vm-settings.png) +* SSH +![gcp-link-vm-ssh](https://res.cloudinary.com/incoder/image/upload/v1542549369/blog/gcp-link-vm-ssh.png) +* SSH密钥添加 +![gcp-link-vm-create-ssh](https://res.cloudinary.com/incoder/image/upload/v1542549960/blog/gcp-link-vm-create-ssh.png) + +#### Xshell 连接服务 + +* 配置连接的服务器地址 +![gcp-link-vm-ssh-ip](https://res.cloudinary.com/incoder/image/upload/v1542554061/blog/gcp-link-vm-ssh-ip.png) +* 配置连接服务器的密钥 +![gcp-link-vm-ssh-login](https://res.cloudinary.com/incoder/image/upload/v1542554117/blog/gcp-link-vm-ssh-login.png) + +### 使用 iTerm + +1. 添加公钥到 SSH 管理 +2. 使用 SSH 命令进行连接 + +![gcp-item2-ssh](https://res.cloudinary.com/incoder/image/upload/v1574527300/blog/gcp-item2-ssh.png) + +>如果你本地没有已有秘钥,或者你需要生产一对 RSA 新秘钥,可参考[MacBook Pro 疑难杂症](https://incoder.org/2019/01/10/mac-question/)文章的 **免密登录服务器** 的前半部分内容 + +## 准备工作 + +### 内核升级 +```bash +# 切换到root用户 +sudo -i +# 安装wget +yum install -y wget +# 安装bbr +wget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh +# 给bbr.sh文件设置权限 +chmod +x bbr.sh +# 启动bbr.sh脚本 +./bbr.sh +``` + +执行`./bbr.sh`安装过程如下 +![gcp-centos-bbr](https://res.cloudinary.com/incoder/image/upload/v1574528726/blog/gcp-centos-bbr.png) + +执行完成后,会提示,输入`y`并`回车`后重启,这时需要等待几分钟 + +重启完成后,重新连接服务器 +```bash +# 切换到root用户 +sudo -i +# 查看内核(版本大于4.13或以上版本,就表示OK) +uname -r +``` + +### 选择安装服务 + +对于 SSR 和 v2ray **都**可以提供不可描述的服务,由于v2ray 功能更加强大,并且更加隐蔽,不易被发现,因此极力推荐使用 v2ray 方式 + +## SSR + +通过以上的配置,我们可以使用Xshell进行SSR工具的安装,安装SSR工具前,需要先升级系统内核,按照如下执行命令 + +### 安装SSR + +```bash +# 切换到root用户 +sudo -i +# wget设置 +wget --no-check-certificate -O shadowsocks-all.sh +# 下载安装SSR +https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks-all.sh +chmod +x shadowsocks-all.sh +# 执行SSR运行脚本 +./shadowsocks-all.sh 2>&1 | tee shadowsocks-all.log +``` +安装过程步骤如下: +* 选择版本:推荐ShadowsocksR,输入`2` +* 设置密码 +* 设置端口 +* 选择加密方式,这里选择chacha20,输入`12` +* 选择协议,这里选择auth_sha1_v4,输入`3` +* 选择混淆方式,这里选择http_simple,输入`2` +![gcp-link-vm-ssh-install](https://res.cloudinary.com/incoder/image/upload/v1542556264/blog/gcp-link-vm-ssh-install.png) + +等待安装完成,提示如下: +![gcp-link-vm-ssh-finish](https://res.cloudinary.com/incoder/image/upload/v1542556345/blog/gcp-link-vm-ssh-finish.png) + +根据安装完成后提示的信息配置你的SSR客户端即可 + +### 修改 SSR 配置 + +在实际过程中,我们安装完成后,可能根据实际环境,需要修改配置,那么我们该怎么去修改呢,直接看下面命令 +```bash +# shadowsocks-r 默认路径是 /etc/shadowsocks-r +vim /etc/shadowsocks-r/config.json +# 安装实际需要,更改后保存配置,然后重启shadowsocks-r 服务 +/etc/init.d/shadowsocks-r restart +# 记得更新你客户端相关的配置 +``` + +### 卸载 SSR + +```bash +# 切换到root用户 +sudo -i +# 进入脚本目录(可省略) +cd /home// +# 使用 help 查看指令(可省略) +./shadowsocks-all.sh -help +# 执行卸载 +./shadowsocks-all.sh uninstall +``` + +>当你不知道该应用拥有什么命令时,多用 help 来获取相关的指令 + +### SSR 常用命令 + +```bash +# 启动SSR +/etc/init.d/shadowsocks-r start +# 退出SSR +/etc/init.d/shadowsocks-r stop +# 重启SSR +/etc/init.d/shadowsocks-r restart +# SSR状态 +/etc/init.d/shadowsocks-r status +# 卸载SSR,默认目录 /home// +./shadowsocks-all.sh uninstall +``` + +## v2ray + +### 时间校准 + +对于 v2ray,它的验证方式包含时间,就算是配置没有任何问题,如果时间不正确,也无法连接 v2ray 服务器的,服务器会认为你这是不合法的请求。所以系统时间一定要正确,只要保证时间误差在90秒之内就没问题 + +```bash +# 查看 VPS 时间 +date -R +# +0000表示时区 +Sat, 23 Nov 2019 17:21:50 +0000 +# 将系统服务时间设置成本地时间 +cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime +# 检查服务器系统时间是否和本地时间一样 +date -R +``` + +### 安装 v2ray + +```bash +# 安装脚本 +bash <(curl -L -s https://install.direct/go.sh) +# 安装完成,v2ray 并不会自动运行,因此需要手动启动服务 +sudo systemctl start v2ray +# 查看 v2ray 运行状态 +service v2ray status +``` + +基本文件安装路径地址 +* /usr/bin/v2ray/v2ray:V2Ray 程序; +* /usr/bin/v2ray/v2ctl:V2Ray 工具; +* /etc/v2ray/config.json:配置文件; +* /usr/bin/v2ray/geoip.dat:IP 数据文件 +* /usr/bin/v2ray/geosite.dat:域名数据文件 + +安装过程截图如下 +![gcp-v2ray-install](https://res.cloudinary.com/incoder/image/upload/v1574530585/blog/gcp-v2ray-install.png) + +### 配置修改 + + +### 客户端连接 + + +## 问题排查 + +每到敏感时期,一大批服务都会被封,这次我的服务也不理外,这里就讲一讲我是如何排除问题。 + +>所处环境说明: +>0. VPS 上安装的 SSR 服务 +>1. 可以使用 SSH 工具(比如:Xshell)可以连接 VPS 机器 +>2. 在 VPS 中 `ping google.com` 是可以的 +>3. 连接 VPS 的客户端,无法正常访问国外网站 + +出现以上情况,大概率是当前的 SSR 服务端口被封了,可以通过以下方式来验证 + +1. 使用国内站长工具端口扫描检查下 VPS 的端口是否可以正常访问,地址:[http://tool.chinaz.com/port](http://tool.chinaz.com/port/) + * 如果提示**关闭**,说明国内无法访问该 VPS 对应的端口服务 + * 如果提示**开启**,说明访问正常 + +2. 使用用国外端口扫描网站进行检查你的 VPS 服务是否可以正常访问,地址:[https://www.yougetsignal.com/tools/open-ports](https://www.yougetsignal.com/tools/open-ports/) + * 如果检查结果为**open**,说明国外可以正常访问你的 VPS 对应端口的服务 + * 如果检查结果为**close**,说明国外无法访问 + +经过上面的两部,可以快速定位到问题,如果你检查出来的结果一样,则可以更改 VPS 上的 SSR 服务端口,重新启动 SSR 服务即可,在上面已经讲到了如何[修改 SSR 配置](#修改SSR配置),切记一起连**加密方式**, +**协议**,**混淆方式**这些配置一并改掉,然后重启 SSR 服务,并修改连接 SSR 服务的**客户端配置**,其实这只是一个暂时的解决方法,我们可以使用更加隐蔽的 [v2ray](#v2ray) 服务 + +## 其它 + +* 查询余额 +进入结算概览页面: https://console.cloud.google.com/billing/ +* 扣费计算 +主机:$5/月. +流量:谷歌云服务器出口大陆流量1T以内价格约为0.23$/1G. +每个月可用流量:$300-$5*12=$240/12/0.23 ≈ 86G +* [SSR客户端](https://www.mediafire.com/folder/btkdbx7j9lr98/Shadowsocks_%E7%9B%B8%E5%85%B3%E5%AE%A2%E6%88%B7%E7%AB%AF#myfiles) + +## 附录 + +* [Google Cloud Platform免费申请&一键搭建SSR & BBR加速教程](https://www.wmsoho.com/google-cloud-platform-ssr-bbr-tutorial) +* [Google Cloud使用VM虚拟机详细操作指南](https://www.rultr.com/tutorials/vps/2303.html) +* [ShadowsocksR客户端 各种隐藏使用技巧说明](https://www.wmsoho.com/how-to-use-shadowsocksr) +* v2ray社区:~~[https://www.v2ray.com](https://www.v2ray.com)已被墙~~,[https://www.v2fly.org](https://www.v2fly.org) +* [自建v2ray服务器教程](https://github.com/Alvin9999/new-pac/wiki/%E8%87%AA%E5%BB%BAv2ray%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%95%99%E7%A8%8B) +* [v2ray各平台图文使用教程](https://github.com/Alvin9999/new-pac/wiki/v2ray%E5%90%84%E5%B9%B3%E5%8F%B0%E5%9B%BE%E6%96%87%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B) \ No newline at end of file diff --git a/source/_posts/damn-base64.md b/source/_posts/damn-base64.md new file mode 100644 index 000000000..f2bd4bb78 --- /dev/null +++ b/source/_posts/damn-base64.md @@ -0,0 +1,28 @@ +--- +title: 该死的 Base64,我惹你了? +date: 2020-11-27 14:43:46 +categories: Java +tag: [Exp] +--- + +在上一个项目中,由于客观原因,双方进行数据交换,用到对媒体文件(图片)进行了 Base64 编码处理,将编码后的数据存入了数据库,使用方再从数据库中取出数据进行解码恢复成图片,在实际处理中,这是**最不推荐**的做法。正确有效的做法是将资源文件存入到 OSS 系统中,数据库中记录文件的地址即可。但由于项目历史原因,无法使用 OSS 来处理,虽然说技术本质不难,编码存入,解码查看而已。但由于对方没有告知具体的编码方式,询问了好几次才最终给到对应的编码方式,浪费了大量的时间去沟通和试错,得不偿失 + + + +对于 Base64 ,开发者或多或少都有听过,严格意义上讲 Base64 不是加密方式,它只是一种编码方式,本篇文章就来详细的聊一聊 Base64 这个熟悉又陌生的朋友 + +## 什么是 Base64 + +## Base64 的原理 + +## 常见的 Base64 + +## 解决实际问题 + +## 参考 + +1. [密码学 | 庐山真面!你认为 Base64 是加密算法吗?](https://www.jianshu.com/p/d9844bf7db6d) +2. [什么是Base64?](https://www.cnblogs.com/chenxibobo/p/14109066.html) +3. [Base64编码原理分析](https://www.cnblogs.com/libin-1/p/6165485.html) +4. [Base64编码](https://www.jianshu.com/p/e95278ed98b4) +5. [Base64算法不一致可能会导致的坑](https://www.jianshu.com/p/b6af30177c0a) \ No newline at end of file diff --git a/source/_posts/data-sync.md b/source/_posts/data-sync.md new file mode 100644 index 000000000..5fa532ccb --- /dev/null +++ b/source/_posts/data-sync.md @@ -0,0 +1,32 @@ +--- +title: 系统间数据同步 +date: 2020-07-21 20:05:00 +categories: Sync +tag: [Sync] +--- + +对于不同系统之间的数据同步,业界通常有四种方式来进行数据交互,本篇文章就来聊一聊这四种数据交互方式 + + + +## 共享文件 + +* 共享目录:可以是同台机器内的硬盘目录或者是挂载一个共享存储。在系统之间同步数据是非常简单,对共享存储中的文件读写就可以了 +* FTP 或者对象存储:不同系统间的批量文件往往用此方式实现,比如银行的批量回盘文件。数据量比较大的情况下大多采用此种方式,这种方式缺点就是不能实时,做个通知接口做到准实时性 +* zookeeper:zk 是基于文件的,可以同步简单数据,大数据量不太合适 + +## 接口 + +接口可以实时进行数据的交互 +* HTTP 接口:这种接口是使用最多的,SpringCloud 所支持的,也是多个已购系统间采用的同步方式。使用方式,可以推送或拉取 +* RPC 方式:微服务的新起,诞生了很多 RPC 框架,也可以说是 RPC 框架促进了微服务的进步。比较流行的 RPC 框架有 Dubbo,gRPC,Thrift 等 + +## 消息队列 + +常用的 MQ 消息队列,在收集日志或者多个系统间准实时同步,优点是系统间解耦,削峰,易扩展等特点 + +## 数据库 + +同步结构化的数据可以采用数据库的形式,比如一个做批量的服务专门计算或统计业务数据,统计结果写入一个库,业务系统需要的时候直接从这个库读取即可 + +数据库同步的方式我理解大多数的单向少数几个系统间的同步。也可以多个系统间使用,比如用数据库的方式生成唯一 ID,多个系统共享使用 \ No newline at end of file diff --git a/source/_posts/deploy-maven.md b/source/_posts/deploy-maven.md new file mode 100644 index 000000000..bcff105f7 --- /dev/null +++ b/source/_posts/deploy-maven.md @@ -0,0 +1,334 @@ +--- +title: 发布 jar 到 Maven中央仓库 +date: 2019-07-21 10:00:46 +categories: Maven +tag: [Deploy] +--- + +## 背景 + +和朋友一起维护的开源组织(我就是打个辅助,逃~),其中有一个系列的项目,这些项目统一通过 base 项目的 pom 文件管理这个系列项目依赖的第三方 jar,其他一些辅助项目(如:tools)项目主要是一些常用工具方法的封装,为了能让我们在不同机器,不同地点能够无缝切换,更重要的让使用的伙伴能以最简便的方式运行(避免不必要的配置),我们需要把通用的东西托管起来,那么就需要将这些配置依赖或辅助 jar 托管到 Maven中央仓库,话不多说,就跟着我的步骤来看看如何将 jar 发布到 Maven中央仓库 + + + +## 准备 + +* Sonatype 账号 +* GPG +* 需要发布的项目 + +> 这里以 Mac 系统演示 + +## Sonatype + +### 账号注册 + +Sonatype 账号注册地址:[https://issues.sonatype.org/secure/Signup!default.jspa](https://issues.sonatype.org/secure/Signup!default.jspa) + +> 记录好你的账号和密码,后续会用到 + +### 创建 issue + +创建 issue 地址:[https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&pid=10134](https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&pid=10134) + +填写信息时,只填写必填项即可 + +1. `Summary`:简单的项目介绍,填写一下 +2. `Group Id`:项目ID,用来定位应用 jar 的坐标,可参考[官方说明](https://central.sonatype.org/pages/choosing-your-coordinates.html) +3. `Project URL`:项目主页地址 +4. `SCM url`:Git仓库地址 + +>注意: +* Group Id + * 无自己的域名:可以使用 Github,比如我的 GitHub 用户名是 BladeCode(也可以用你的组织名,如这里:twodragonlake),那么这里的Group Id应该填写 com.github.BladeCode,也可以使用 io.github.BladeCode + * 有自己的域名:按照要求添加一条 TXT 的 DNS 解析,用来验证你的 `Group Id` + ![ossrh-domain](https://res.cloudinary.com/incoder/image/upload/v1563762211/blog/ossrh-domain.png) +* 可参考:[OSSRH-45597](https://issues.sonatype.org/browse/OSSRH-45597) + +### 验证 Group Id + +根据你是否有自己的域名,有不同的方式来验证,上面的创建 issue 的注意中已经说明了,这里不啰嗦了,直接看下图 +![ossrh-ticket](https://res.cloudinary.com/incoder/image/upload/v1563705782/blog/ossrh-ticket.png) + +## GPG + +* Windows:[Gpg4win](https://www.gpg4win.org/download.html) +* macOS:gpg + +### 安装 + +macOS 为例 +```sh +# 安装 +brew install gpg +# 验证 +gpg --version +``` + +### 生成秘钥对 + +```sh +gpg --gen-key +``` +![ossrh-gpg-key](https://res.cloudinary.com/incoder/image/upload/v1563706827/blog/ossrh-gpg-key.png) + +这里用于生成秘钥的用户名和邮箱,可以和你的 `Sonatype` 账号不一样,记录`密码`,在部署时需要用到 + +### 上传秘钥 + +```sh +# 上传秘钥,最好带上端口号 +gpg --keyserver hkp://pool.sks-keyservers.net:11371 --send-keys <密钥ID> +# 验证秘钥,最好带上端口号 +gpg --keyserver hkp://pool.sks-keyservers.net:11371 --recv-keys <密钥ID> +``` +![ossrh-gpg-send](https://res.cloudinary.com/incoder/image/upload/v1563707069/blog/ossrh-gpg-send.png) + +上传到其他服务器,命令同上,更换地址即可 +* hkp://keyserver.ubuntu.com:11371 +* hkp://keys.gnupg.net:11371 + +如果你忘记了你刚刚生成的秘钥,可以使用下面的命令来查看本地生成的所有秘钥 +```sh +gpg --list-keys +``` + +## 配置 + +### maven 配置 + +* 查看路径 + macOS 可以使用 IDEA 查看 maven 的路径,`/usr/local/Cellar/maven/3.6.0/libexec/conf` + ![ossrh-maven-local](https://res.cloudinary.com/incoder/image/upload/v1563707961/blog/ossrh-maven-local.png) +* 修改 `settings.xml` 文件 + 在 `` 标签内,添加如下配置 + ```xml + + oss + 你注册的Sonatype账号 + 密码 + + ``` + +### pom 配置 + +配置你需要上传项目的 `pom` 文件 + +```xml + + org.sonatype.oss + oss-parent + 7 + + + tdl-base + TwoDragonLake base pom + https://github.com/TwoDragonLake/tdl-base + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + + + Jerry xu + incoder.xu@gmail.com + + + + + master + https://github.com/TwoDragonLake/tdl-base.git + scm:git:https://github.com/TwoDragonLake/tdl-base.git + scm:git:https://github.com/TwoDragonLake/tdl-base.git + + + + + + release + + + + + maven-clean-plugin + 3.0.0 + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + package + + jar-no-fork + + + + + + maven-compiler-plugin + 3.7.0 + + + maven-surefire-plugin + 2.20.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + org.apache.maven.plugins + maven-source-plugin + + + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + + + package + + jar + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + verify + + sign + + + + + + + + + + oss + https://oss.sonatype.org/content/repositories/snapshots/ + + + oss + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + +``` + +### 编译并部署 + +```sh +mvn clean deploy -P release -Dmaven.test.skip=true +``` + +然后在命令行的弹出中输入使用 gpg 命令生成秘钥时输入的密码,如果在命令行中没有弹框提示,那么可以在终端中输入`export GPG_TTY=$(tty)`命令,再次执行部署命令,部署完成参考下图提示 +![ossrh-deploy](https://res.cloudinary.com/incoder/image/upload/v1563725146/blog/ossrh-deploy.png) + +## 发布 + +### 编译构建验签 + +部署成功后,使用`Sonatype`登录 [https://oss.sonatype.org](https://oss.sonatype.org/#stagingRepositories)网站,进行发布,在`Build Promotion`中选择`Staging Repositories`,然后选择对应你的`groud id`的`Repository`,进行 close,这里的`Close`其实就是进行自动构建,进行验证 +![ossrh-close](https://res.cloudinary.com/incoder/image/upload/v1563725940/blog/ossrh-close.png) + +参看是自动化运行过程否有错误,正确如下截图没有错误提示,如果有错误提示,就按照提示内容进行处理 +![ossrh-build](https://res.cloudinary.com/incoder/image/upload/v1563726108/blog/ossrh-build.png) + +### 发布 + +构建成功无错误,后就可以发布了,其实发布和部署是一样的操作,只不过部署是进行`Close`,而发布是`Release`操作,此时会提示你发布成功后会删除`Staging Repositories` 的 `Repository` 记录 +![ossrh-release](https://res.cloudinary.com/incoder/image/upload/v1563726307/blog/ossrh-release.png) + +### 查看 + +打开你的[https://issues.sonatype.org](https://issues.sonatype.org/browse/OSSRH-45597),登录并查看,你的 issues 下,提示你,已经发布成功,稍后可以在[https://search.maven.org](https://search.maven.org)中搜索到 +![ossrh-deploy-success](https://res.cloudinary.com/incoder/image/upload/v1563726729/blog/ossrh-deploy-success.png) + +搜索结果,可以查看到我们发布的包 +![ossrh-search](https://res.cloudinary.com/incoder/image/upload/v1563726893/blog/ossrh-search.png) + +## 异常 + +### IDEA 中 not found + +项目中引入的 jar,IDEA 中提示无法找到包,可以打开 IDEA 的 Preferences 中进行同步 +![ossrh-idea-update](https://res.cloudinary.com/incoder/image/upload/v1563727259/blog/ossrh-idea-update.png) + +### 本地编译错误 + +* 无提示框提示输入密码 + ![ossrh-local-build-comfrim](https://res.cloudinary.com/incoder/image/upload/v1563727360/blog/ossrh-local-build-comfrim.png) + 解决方法:`export GPG_TTY=$(tty)` 命令,重新编译 +* 无法连接sonatype + ![ossrh-local-sonatype](https://res.cloudinary.com/incoder/image/upload/v1563727678/blog/ossrh-local-sonatype.png) + 解决方法:查看`settings.xml`文件中 ``标签中配置的 `id` 是否与项目`pom`文件的`` 标签下的 `id` 是否一致 + +### sonatype 构建错误 + +查看构建过程中错误提示,我这里是应为无法验证签名,因此我将提示中的服务器地址,全部都再发布`gpg`的秘钥,注意地址开头是 `hkp` +![ossrh-build-error](https://res.cloudinary.com/incoder/image/upload/v1563728038/blog/ossrh-build-error.png) + +## 附录 + +### 官方 + +* [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) +* [发布要求、规范](https://central.sonatype.org/pages/apache-maven.html) +* [PGP签名使用](http://central.sonatype.org/pages/working-with-pgp-signatures.html) +* [发布项目文档](http://central.sonatype.org/pages/producers.html) + +### 其他 + +* [发布 Maven 构件到中央仓库](http://www.r9it.com/20190701/maven-artifact-deploy.html) +* [发布构件到Maven中央仓库](https://silloy.me/2018/06/19/%E5%8F%91%E5%B8%83%E6%9E%84%E4%BB%B6%E5%88%B0Maven%E4%B8%AD%E5%A4%AE%E4%BB%93%E5%BA%93/) +* [如何发布Jar包到Maven Central Repository](https://www.jianshu.com/p/1bd36edab4ee) diff --git a/source/_posts/deploy-springboot.md b/source/_posts/deploy-springboot.md new file mode 100644 index 000000000..b9fa29cb0 --- /dev/null +++ b/source/_posts/deploy-springboot.md @@ -0,0 +1,179 @@ +--- +title: IDEA 之 SpringBoot 应用部署 +date: 2019-11-19 20:32:10 +categories: IDEA +tag: [SpringBoot, Deploy] +--- + +服务端由原来 **混合式**(Java+JSP)的方式演进成专注于提供服务 API(**前后端分离**)的方式,开发的明确分工,使的各自开发人员在各自领域的垂直技能的加强,以满足业务的快速迭代,因此也就在这两个方式中,项目的构建方式也有了一定的变化,混合模式中常编译为 war 包,而在前后端分离模式中常编译为 jar 包,这两种文件格式虽然都是一种压缩文件的格式,但实质还是有一些区别,那首先让我们来了解这两种文件它们之间的区别 + + + +jar 文件与 war 文件的区别 +1. war 文件常应用在 **混合式** 的项目中,war 文件包含Java 相关的项目文件、部署文件,还包含一些前端页面等引用的相关资源文件;jar 文件常应用在 **前后端分离** 的项目中,jar 文件主要包含Java 相关的项目文件、部署文件 +2. war 文件中不包含 Tomcat相关文件,必须运行在 Tomcat 容器中;jar 文件中内置了 Tomcat 文件,可直接运行 +3. war 文件通常使用 SSM 架构;jar 文件通常使用 SpringBoot/SpringCloud 架构 +4. 无论是 jar 还是 war 都能够使用嵌套容器,java -jar来独立运行 +5. **只有 war** 才能部署到外部容器中;SpringBoot支持多种模板引擎,但JSP 只能在 war 中使用 + +## 准备 + +* System:macOS +* Java:JDK 1.8+ +* 编辑器:IDEA +* 包管理:maven/gradle +* 服务器连接:iTerm2 +* 项目示例: + * [rc-cluster-springboot](https://github.com/RootCluster/rc-cluster-springboot),这是一个 gradle 管理的 SpringBoot 项目 + * [rc-ssm](https://github.com/RootCluster/rc-ssm/tree/example),这是一个 maven 管理的 SSM 项目 + +>Windows 连接服务器工具可使用 [Xshell](https://www.netsarang.com/zh/xshell) 等代替 + +## 编译 + +### 编译 war + +```xml + +war +``` + +```bash +# clean依赖并编译成 package,也可以执行 mvn clean package -DskipTests +mvn clean package -Dmaven.test.skip=true +``` + +![deploy-maven-war](https://res.cloudinary.com/incoder/image/upload/v1574837752/blog/deploy-maven-war.png) + +### 编译 jar(SpringBoot) + +#### maven + +```xml + +jar +``` + +##### GUI 操作 + +![deploy-maven-jar](https://res.cloudinary.com/incoder/image/upload/v1585097721/blog/deploy-maven-jar.png) + +##### 命令行 操作 + +定位到需要构建的模块下,执行如下命令 + +```bash +# clean依赖并编译成 package,也可以执行 mvn clean package -DskipTests +mvn clean package -Dmaven.test.skip=true +``` + +![maven-package-jar](https://res.cloudinary.com/incoder/image/upload/v1585097855/blog/maven-package-jar.png) + +#### gradle + +##### GUI 操作 + +![deploy-gradle](https://res.cloudinary.com/incoder/image/upload/v1585097492/blog/deploy-gradle.png) + +##### 命令行 操作 + +在需要编译的项目路径下,这里编译子模块springboot-start,前提需要在系统的环境变量中配置好 gradle,macOS 可参考[MacBook Pro 初始化](https://incoder.org/2018/11/10/mac-init/#Gradle配置)文章 + +```bash +# linux or macOS +gradle build +# Windows +./gradle build +``` + +![deploy-gradle-build](https://res.cloudinary.com/incoder/image/upload/v1574959663/blog/deploy-gradle-build-success.png) + +## 部署 + +部署步骤: +1. 备份服务器对应服务,并停止服务运行(如果是第一次部署该服务,可省略) +2. 上传应用 jar/war 文件 +3. 启动上传应用 + +```bash +# 拷贝本地文件到指定服务器的指定目录 +# root:服务器用户名 +# ip:服务器地址 +# :/data/app:拷贝文件到服务器的/data/app 路径下 +scp ~/Desktop/start-1.0-SNAPSHOT.jar root@ip:/data/app +``` + +### 部署 war + +>由于 war 包并没有内置 Tomcat 等运行的容器,因此需要你的服务器已经安装了 Tomcat 等服务 + + + +### 部署 jar + +#### iTerm2 部署 + +```bash +# ssh 连接到服务器后,启动应用,如果有需要请先停止已在运行的程序 +# 查看程序的进程 +ps -ef|grep 'java -jar' +# UID PID PPID C STIME TTY TIME CMD +# root 6760 7103 0 19:59 pts/3 00:00:00 java -jar /data/app/start-1.0-SNAPSHOT.jar + +# 终止程序进程 +kill -9 pid + +# nohup:后台运行程序,也就说当控制台终止,并不会停止启动的服务 +# spring.log:当前目录下输出日志记录的文件名 +nohup java -jar start-1.0-SNAPSHOT.jar > spring.log 2>&1 & + +# 滚动查看日志输出 +tail -f spring.log + +# 如果不需要查看输入出的日志,运行如下脚本 +nohup java -jar start-1.0-SNAPSHOT.jar & +``` + +tail 命令 +* 命令格式:tail[必要参数][选择参数][文件] +* 功能描述:用于显示指定文件末尾内容,不指定文件时,作为输入信息进行处理;查看日志文 +* 示例:tail -f spring.log +* 命令参数 + * -f 循环读取 + * -q 不显示处理信息 + * -v 显示详细的处理信息 + * -c<数目> 显示的字节数 + * -n<行数> 显示行数 + * –pid=PID 与-f合用,表示在进程ID,PID死掉之后结束. + * -q, –quiet, –silent 从不输出给出文件名的首部 + * -s, –sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒 + +#### Alibaba Cloud Toolkit + +![alibaba-cloud-toolkit](https://res.cloudinary.com/incoder/image/upload/v1585098223/blog/alibaba-cloud-toolkit.png) + +视频教程:https://cloud.video.taobao.com/play/u/650480598/p/1/e/6/t/1/216889058961.mp4 + +#### Jenkins + +关于 Jenkins 的部署,会单独出一篇相关的部署介绍,以及 Jenkins 的相关知识介绍,请移步[使用 Jenkins 项目部署]() + +## 问题 + +### gradle 编译失败 + +![deploy-gradle-build-error](https://res.cloudinary.com/incoder/image/upload/v1574960394/blog/deploy-gradle-build-error.png) + +原因:这里的提示`spring-boot-2.1.6.RELEASE.jar`文件无法正常下载 +解决办法:切换你的网络,或者修改 gradle 仓库镜像地址 + +### 缓存文件未下载完全 + +![](https://res.cloudinary.com/incoder/image/upload/v1574961213/blog/deploy-gradle-build-error2.png) + +原因:文件未下载完全,编译时使用了缓存文件 +解决办法:删除`~/.gradle/caches/modules-2/files-2.1`路径下无法编译通过的包,这里是`spring-boot-gradle-plugin` + +## 附录 + +* [SpringBoot入门系列(四)---Spring Boot 项目打包运行](https://www.lixueduan.com/posts/144a69f9.html) \ No newline at end of file diff --git a/source/_posts/docker-init.md b/source/_posts/docker-init.md new file mode 100644 index 000000000..abd8f6593 --- /dev/null +++ b/source/_posts/docker-init.md @@ -0,0 +1,6 @@ +--- +title: Docker 之 SpringBoot 项目部署 +date: 2019-01-10 01:04:16 +categories: Docker +tag: [SpringBoot, Deploy] +--- \ No newline at end of file diff --git a/source/_posts/fiddler.md b/source/_posts/fiddler.md new file mode 100644 index 000000000..1c5c0a6e9 --- /dev/null +++ b/source/_posts/fiddler.md @@ -0,0 +1,59 @@ +--- +title: Fiddler 初体验 +date: 2018-10-25 19:40:46 +categories: DevTool +tag: [Fiddler] +--- + +在开发的路上,有时候面对一些应用,我们可能回去分析研究它的实现以及数据交互等,在没有官方没有公开的Api提供时,我们会用到一项实用的技术,抓包,所谓的抓包,指的是截取网络传输发送与接收的数据包。其中在Windows平台上使用比较广泛的要数[Fiddler](https://www.telerik.com/fiddler) + +本节主要讲解Fiddler的相关配置及简单使用 + + + +## 资源 + +* Windows 10 +* Fiddler + +## 配置 + +需要使手机连接WiFi和电脑WiFi是使用同一个网络 + +### Fiddler 初始化 + +![fiddler-config](https://res.cloudinary.com/incoder/image/upload/v1540742306/blog/fiddler-config.png) +>默认端口:8888 + +### 网络地址 + +获取电脑所连接的网络IP地址 +![fiddler-ip](https://res.cloudinary.com/incoder/image/upload/v1540742723/blog/fiddler-ip.png) +这里获取的IP地址,将用于手机连接网络的代理 + +### 手机配置 + +关于手机相关的配置操作,步骤已经通过下面的视频展现。 +1. 连接与电脑相同的WiFi +2. 修改网络代理 +3. 手动模式,并设置电脑端获取的IP地址及Fiddler默认端口号8888 +4. 网络连接刷新 +5. 获取并下载安装Fiddler证书 + +## 其它 + +通过以上操作,现在可以在电脑端Fiddler工具中,拦截获取经过的所有网络信息。而我们一般是查看或者是分析某一款应用的数据信息,这样在查看起来就比较费力,那么我们就借助Fiddler提供的过滤功能 +![fiddler-filter](https://res.cloudinary.com/incoder/image/upload/v1540743106/blog/fiddler-filter.png) + +选择过滤方式中 +1. 第一项有三个选项,不做更改: + “No zone filter”; + “Show Only Intranet Hosts”; + “Show Only Internet Hosts” +2. 第二个选项是只监控以下网址,如只监控百度,在下面的输入框里填上 www.baidu.com + “No Host Filter”:不设置hosts过滤 + “Hide The Following Hosts”:隐藏过滤到的域名 + “Show Only The Following Hosts”:只显示过滤到的域名 + “Flag The Following Hosts”:标记过滤到的域名 +3. 文本框内输入需要过滤的域名,多个域名使用”;“分号分割。 +>fiddler默认会检查http头中设置的host,强制显示http地址中域名。 \ No newline at end of file diff --git a/source/_posts/flowable1.md b/source/_posts/flowable1.md new file mode 100644 index 000000000..5415e85a8 --- /dev/null +++ b/source/_posts/flowable1.md @@ -0,0 +1,75 @@ +--- +title: Flowable(一)初识 +date: 2019-09-25 12:40:46 +categories: Flowable +tag: [Flowable] +--- + +Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据等,众所周知,Flowable是Activit的一个分叉,[Flowable的第一个版本(5.22.0)是基于Activit(5.21.0)](https://blog.flowable.org/2016/10/13/flowable-5-22-0-release/),关于为什么Flowable会从Activit分叉,感兴趣可以查看Flowable官方的文章[Flowable and Activiti: What the Fork?!](https://blog.flowable.org/2016/10/12/flowable-and-activiti-what-the-fork/),这里不在赘述这些内容 + + + +从[Flowable官方文档](https://www.flowable.org/documentation.html)介绍,可知Flowable遵循[BPMN](https://www.flowable.org/docs/userguide/index.html),[CMMN](https://www.flowable.org/docs/userguide-cmmn/index.html),[DMN](https://www.flowable.org/docs/userguide-dmn/index.html),[From](https://www.flowable.org/docs/userguide-form/index.html)设计指导 +* BPMN(Business Process Model and Notation):用于流程管理 +* CMMN(Case Management Model and Notation):用于案例管理 +* DMN(Decision Model and Notation):用于决策规则 +* Form:用于表单和任务表单管理 + +## Flowable 直运行 + +>这里所说的"**直运行**",是指不需要写任何代码,仅需要改动相关的配置就可以运行起Flowable应用程序 + +准备工作 +* [Flowable v6.4.2](https://www.flowable.org/downloads.html) +* MySQL8+ +* JDK & Tomcat 环境 + +> MySQL8+ ,JDK,Tomcat环境代建可参考[Linux 之 MySQL](https://incoder.org/2018/07/23/linux-mysql/),[Linux 常用应用安装](https://incoder.org/2018/05/15/linux-build/),[Windows 之 常用应用安装](https://incoder.org/2019/09/25/windows-devtool/) + +已下操作均在Windows上,macOS上相差不大,操作流程基本一致 + +### war部署 + +1. 解压flowable.zip文件 + ![flowable-zip](https://res.cloudinary.com/incoder/image/upload/v1569565014/blog/flowable-zip.png) +2. 拷贝需要启动的war到安装的Tomcat的`webapps`路径下 + ![flowable-tomact](https://res.cloudinary.com/incoder/image/upload/v1569554378/blog/flowable-tomact.png) +3. 命令行中执行`startup.bat`命令,或执行Tomcat的`bin`路径下,启动`startup.bat`文件 + ![flowable-startup](https://res.cloudinary.com/incoder/image/upload/v1569554648/blog/flowable-startup.png) +4. 第一次启动,Tomcat控制台应该会出错,因为`flowable-admin.war`数据库配置默认使用H2数据库,我们需要修改数据库配置连接等信息 + ![flowable-mysql-config](https://res.cloudinary.com/incoder/image/upload/v1569555241/blog/flowable-mysql-config.png) + >* 文件地址:`/webapps/flowable-admin/WEB-INF/classes`路径,`flowable-default.properties`文件及`application-dev.properties`文件 + >* MySQL中需要一个名为 **flowable** 的数据库,没有请创建一个`CREATE DATABASE flowable` + >* 由于我使用的是 MySQL8 ,Tomcat 中不包含此驱动 jar 包,因此需要手动下载[mysql-connector-java-8.x.x(和你数据库匹配版本).zip](http://ftp.jaist.ac.jp/pub/mysql/Downloads/Connector-J/)文件进行解压,拷贝`mysql-connector-java-8.x.x.jar`文件到 `/lib`路径下 +5. 重新在命令行中执行`startup.bat`命令,或执行Tomcat的`bin`路径下,启动`startup.bat`文件 +6. 正常情况到此等待服务器启动完成,如果不能正常启动,请查看Tomcat控制台是否有错误,按照提示解决错误,直到Tomcat不再有错误提示即可 + + +### 使用 + +1. 访问[http://localhost:8080/flowable-idm](http://localhost:8080/flowable-idm),默认账号:admin,默认密码:test + ![flowable-admin](https://res.cloudinary.com/incoder/image/upload/v1569563245/blog/flowable-admin.png) +2. 访问[http://localhost:8080/flowable-admin](http://localhost:8080/flowable-admin),后台管理 +3. 访问[http://localhost:8080/flowable-modeler](http://localhost:8080/flowable-modeler),流程定义管理 +4. 访问[http://localhost:8080/flowable-task](http://localhost:8080/flowable-task),用户任务管理 +5. 访问[http://localhost:8080/flowable-rest/docs](http://localhost:8080/flowable-rest/docs),流程引擎对外提供的API接口 + +## Flowable 集成运行 + +>这里所说的"**集成运行**",是指通过Flowable官方提供的jar文件,集成到我们的项目中运行的方式 + +## Flowable 使用 + +## 其他 + +### 如何切换中文 + +Flowable中已包含中文语言,会根据操作系统语言,自动显示对应语言 + +### startup.bat异常 + +查看控制它异常,例如当前flowable启动默认端口8080,被占用 +![flowable-aleady-bind](https://res.cloudinary.com/incoder/image/upload/v1569556130/blog/flowable-aleady-bind.png) + +解决方法:查找占用端口进程`netstat -ano|findstr 端口号`,并kill它`taskkill -PID 进程号 -F` +![flowable-kill-task](https://res.cloudinary.com/incoder/image/upload/v1569556500/blog/flowable-kill-task.png) \ No newline at end of file diff --git a/source/_posts/flowable2.md b/source/_posts/flowable2.md new file mode 100644 index 000000000..91ae3ceb1 --- /dev/null +++ b/source/_posts/flowable2.md @@ -0,0 +1,8 @@ +--- +title: Flowable(二)Modeler-UI 集成 +date: 2019-10-24 12:40:46 +categories: Flowable +tag: [Flowable] +--- + + \ No newline at end of file diff --git a/source/_posts/flowable3.md b/source/_posts/flowable3.md new file mode 100644 index 000000000..f36e12996 --- /dev/null +++ b/source/_posts/flowable3.md @@ -0,0 +1,13 @@ +--- +title: Flowable(三)流程图绘制异常问题 +date: 2020-07-01 09:11:46 +categories: Flowable +tag: [Flowable] +--- + + + +## 附录 + +* [HttpMediaTypeNotAcceptableException](https://my.oschina.net/u/3759357/blog/4281110) +* [HttpMediaTypeNotAcceptableException in Spring MVC](https://www.baeldung.com/spring-httpmediatypenotacceptable) \ No newline at end of file diff --git a/source/_posts/flowable4.md b/source/_posts/flowable4.md new file mode 100644 index 000000000..e5a9e9491 --- /dev/null +++ b/source/_posts/flowable4.md @@ -0,0 +1,42 @@ +--- +title: Flowable(四)流程相关知识点 +date: 2020-07-21 20:11:46 +categories: Flowable +tag: [Flowable] +--- + +## 流程部署 + +流程部署涉及到的表有 `act_re_deployment`, `act_re_procdef`,`act_ge_bytearry`,`act_ge_property` + +* act_re_deployment(部署对象表):存放流程定义的显示名和部署时间,每部署一次增加一条记录 +* act_re_procdef(流程定义表):存放流程定义的属性信息,部署每个新的流程都会在这张表中添加一条记录,当流程定义的 key 相同时,使用的是版本升级 +* act_ge_bytearry(资源文件表):存放流程定义相关的部署信息,即流程定义文档的存放处。每部署一次会增加两条记录,一条是关于 bpmn 规则文件,一条是生成的流程图片(如果部署时只指定了 bpmn 一个文件,flowable 会在部署时解析 bpmn 文件内容自动生成流程图)。两个文件都是以二进制形式存储在数据库中 +* act_ge_property:主键生成策略表 + + + +## 流程定义 + +### 流程定义 + +流程的一系列规则定义 + +### 流程实例 + +代表流程定义的执行实例,例如:员工 A 请假一天,他就必须发出一个请假流程实例申请 + +一个流程实例表示了所有的运行节点,我们可以利用这个对象来了解当前流程实例的进度等信息。流程实例就表示一个流程从开始到结束的最大流程分支,即一个流程中流程实例只有一个。流程实例通常也叫做执行实例的根节点,流程实例和流程定义为一对多的关系 + +执行实例(ProcessInstance Extends Execution),启动流程的时候会首先创建流程实例,然后创建执行实例;流程运转中永远执行的是自己对应的执行实例;当所有的执行实例按照规则执行完毕后,则实例随之结束;flowable 用这个对象去描述流程执行的每一个节点;流程按照流程定义的规则执行一次的过程,就可以表示执行对象 Execution。一个流程中,执行对象可以存着多个,但是实例只能有一个 + +## 节点 + +### 开始节点 + +开始节点代表一个规则的开始。在一个规则文件中,开始节点只能是一个,不能是多个。如果是多个则部署的时候会报错。子流程及引用流程也是如此。开始节点只能是一个。启动流程的时候,从开始节点让流程实例运行 + +### 结束节点 + +结束节点代表一个规则的结束。在一个规则文件中,结束节点可以是多个。如果实例运转到结束节点的时候,则表示当前的执行实例要结束,则流程也将随之结束 + diff --git a/source/_posts/flutter-init.md b/source/_posts/flutter-init.md new file mode 100644 index 000000000..0cad0c51e --- /dev/null +++ b/source/_posts/flutter-init.md @@ -0,0 +1,114 @@ +--- +title: Flutter(一)之环境搭建 +date: 2018-12-16 02:14:59 +categories: Android +tag: [flutter] +--- + +这两年随着前端的高速发展,大前端的趋势下,Native移动应用开发市场在一定程度上被前端瓜分,加之硬件的快速迭代,性能已不存在明显的短板,[React Native](https://facebook.github.io/react-native),[Vue](https://cn.vuejs.org/index.html),[Angular](https://angular.io/)等等这些Web框架,对移动端也有了较大的提升,毕竟这样的开发效率会直线上升,并且大大减少了成本。技术的革新真的好快,如果不去学习,很快就会被淘汰 + +那就直接进入正题,[flutter](https://flutter.io/)是一站式跨平台解决方案,一次开发,适配整个移动平台,并且是由Google进行主导开发,开源的一个项目,现如今已经迭代到1.0版本 + +本篇文章主要记录在macOS系统上搭建flutter开发环境的过程 + + + +## 准备 + +* Android Studio开发环境(JDK,AndroidSDK,Gradle等等,这里不再赘述) +* [flutter SDK](https://flutter.io/docs/get-started/install) +* Android Studio Plugin --> Flutter + +## 步骤 + +1. 解压下载的flutter SDK,并配置环境变量,例如这里配置在`.bash_profile`文件中 + ```bash + # 打开 .bash_profile文件 + vim .bash_profile + # .bash_profile文件中加入flutter sdk路径并保存 + export FLUTTER_HOME=/Users/blade/Documents/DevTools/flutter + export PATH=$FLUTTER_HOME/bin:$PATH + # 重新加载.bash_profile文件 + source .bash_profile + ``` +2. 检查环境变量是否配置正确,如果有相关命令说明,表示已配置好环境变量 + ```bash + flutter -h + ``` +3. 检查开发环境,第一次执行,应该提示如下图所示说明 + ```bash + flutter doctor + ``` + ![flutter-doctor](https://res.cloudinary.com/incoder/image/upload/v1544994568/blog/flutter-doctor.png) + 其实不难,看出我们需要安装一下其他辅助工具等 +4. 解决问题,按照如下命令,一步步执行,大概得1个小时左右(取决于你的网络情况) + ```bash + # 允许协议(android-licenses + flutter doctor --android-licenses + # 安装libimobiledevice + brew install --HEAD libimobiledevice + # 安装ideviceinstaller + brew install ideviceinstaller + # 安装ios-deploy + brew install ios-deploy + # 安装cocoapods + brew install cocoapods + # cocoapods 初始化,这一步比较耗时,需要下载文件大致547M,需要耐心等待 + pod setup + ``` +5. 以上步骤都正常运行后,再次检查环境,如下图所示结果,表示已完成flutter环境搭建 + ```bash + flutter doctor + ``` + + ![flutter-finish](https://res.cloudinary.com/incoder/image/upload/v1544994676/blog/flutter-finish.png) + +## 辅助 + +如果你不习惯或者不想使用Android Studio来开发Flutter,那么使用[VS Code](https://code.visualstudio.com)是最佳推荐的文本编辑器,只需要在VS Code中安装[Flutter](https://marketplace.visualstudio.com/items?itemName=dart-code.flutter)插件即可,它已包含所需的[Dart](https://marketplace.visualstudio.com/items?itemName=dart-code.dart-code)语法插件 + +关于程序的运行,那么模拟器当然少不了,这里介绍下macOS上如何启动Android 模拟器 +* 首先AndroidSDK的环境变量配置少不了 +* 配置emulator + ```bash + export ANDROID_HOME=/Users/blade/Library/Android/sdk + export FLUTTER_HOME=/Users/blade/Documents/DevTools/flutter + export PATH=$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$FLUTTER_HOME/bin:$PATH + ``` +* 启动 + ```bash + # 查看已创建模拟器清单 + emulator -list-avds + # 选择需要启动的模拟器,avd_name:表示从上面列表获取到的模拟器名称 + emulator -avd [avd_name] + ``` +{% note warning %} +注意: +* 不推荐使用[Genymotion](https://www.genymotion.com/),flutter的运行在此模拟器上有各种灵异bug +* PANIC: Missing emulator engine program for 'x86' CPU.解决方式:创建一个x64的模拟器 +{% endnote %} + +## 问题 + +### libusbmuxd version error during flutter install +```bash +brew update +brew uninstall --ignore-dependencies libimobiledevice +brew uninstall --ignore-dependencies usbmuxd +brew install --HEAD usbmuxd +brew unlink usbmuxd +brew link usbmuxd +brew install --HEAD libimobiledevice +``` + +### Unbrewed header files were found in /usr/local/include + +![flutter-node](https://res.cloudinary.com/incoder/image/upload/v1544994898/blog/flutter-node.png) + +## 附录 + +* [flutter docs](https://flutter.io/docs) +* [Flutter免费视频第一季-环境搭建](http://jspang.com/post/flutter1.html#toc-586) +* [flutter安装记录过程](https://www.jianshu.com/p/637796e9c0ea) +* [macOS上搭建Flutter开发环境](https://flutterchina.club/setup-macos/#%E8%AE%BE%E7%BD%AE%E6%82%A8%E7%9A%84android%E8%AE%BE%E5%A4%87) +* [官方命令行构建您的应用](https://developer.android.google.cn/studio/build/building-cmdline?hl=zh-cn) \ No newline at end of file diff --git a/source/_posts/freedom-pact.md b/source/_posts/freedom-pact.md new file mode 100644 index 000000000..b095afdd9 --- /dev/null +++ b/source/_posts/freedom-pact.md @@ -0,0 +1,144 @@ +--- +title: 评论不自由,赞美无意义 +date: 2021-05-05 12:30:10 +categories: Agent +tag: [VPN] +--- + +就像文章标题所述。每到三,五月这个时间,网络变得异常脆弱。各种“奇怪”的网站访问起来很费劲。对于一个技术人员,这些问题可以解决,但是每次都需要花费一定的时间和精力来应对这些,而且随着 [GFW](https://baike.baidu.com/item/GWF) 的不断升级和加强,应对的策略和技术也是在不断迭代,前前后后已经出现了多种技术,本篇就以这个契机,梳理截止到 2021-05-05 所了解到关于 “代理” 相关的知识。现在主流的科学上网技术有 VPN、SS、SSR、V2Ray、Trojan、Trojan-Go,小众的 WireGuard、Brook、Snell 和 NaiveProxy 等 + + + +{% note info %} +本篇文章大量使用了 [一灯不是和尚](https://iyideng.net/about/myhome.html) 作者发布 《[科学上网工具哪个好?](https://iyideng.net/black-technology/cgfw/vpn-ss-ssr-v2ray-trojan-wireguard-bypass-gfw.html) 》文章中的内容,在此感谢作者对各技术的汇总以及经验总结,本篇文章是在原文章的基础上进行的扩展补充 +{% endnote %} + +## VPN + +[虚拟专用网络](https://zh.wikipedia.org/wiki/%E8%99%9B%E6%93%AC%E7%A7%81%E4%BA%BA%E7%B6%B2%E8%B7%AF)(Virtual Private Network,缩写:VPN)是常用于连接中,大型企业或团体间私人网络的通讯方法。它利用隧道协议(Tunneling Protocol)来达到发送端认证,消息保密与准确性等功能。 + +VPN 只是一个统称,它有多种具体实现。比如: + +* PPTP(点对点隧道协议:Point to Point Tunneling Protocol); +* L2TP(第二层隧道协议:Layer Two Tunneling Protocol); +* IPsec(互联网安全协议:Internet Protocol Security); +* WireGuard(一种协议); +* OpenVPN(一种虚拟专用网络 **V**irtual **P**rivate **N**etwork(VPN)系统); +* IKEv2(因特网密钥交换:Internet Key Exchange) 等 + +### WireGuard + +WireGuard 是由 Jason A. Donenfeld 开发,是最新的协议实现(在 2020 年,WireGuard 协议已被添加到 Linux 和 Android 内核中,从而为 VPN 提供商所采用。默认情况下,WireGuard 使用 Curve25519 进行秘钥交换,并使用 ChaCha20 进行加密,但还具有客户端和服务器之间预共享对称秘钥的功能) + +### OpenVPN + +OpenVPN 是由 James Yonan 编写,实现了在路由或桥接配置和远程访问设施中创建安全点对点或站点对站点连接的技术。它实现了客户端和服务器应用程序。它不与IPsec兼容 + +OpenVPN 允许对等方使用预先共享的密钥、证书或用户名/密码相互验证。当在多客户端服务器配置中使用时,它允许服务器使用签名和证书颁发机构为每个客户端发布身份验证证书。 + +## SS + +SS 是 [Shadowsocks](https://github.com/shadowsocks) 的缩写,中文名为影梭,为了避免关键词过滤,网友喜欢将 Shadowsocks 称为 “酸酸”,是一种基于 Socks5 代理方式的加密传输协议,也可以指定实现这个协议的各种开发包。Shadowsocks 是由 [Clowwindy](https://github.com/clowwindy) 为了自己使用谷歌查资料而编写;Shadowsocks 分为服务端和客户端,在使用之前,需要先将服务器端程序部署到服务器上面,然后通过客户端连接并创建本地代理。后来,他觉得这个东西非常好用,速度也很快,于是将源码提交到了 GitHub。由于其优秀的使用体验,Shadowsocks 被广泛传播,导致作者被某部门请去 “喝茶”。迫于压力 Clowwindy 于 2015-08-22 宣布停止维护此项目,并移除其个人页面所存储的源代码,而且保证永不再参与维护更新 + +虽然 Clowwindy 被迫放弃了 Shadowsocks,但开源界没有放弃,各路大神依旧在为 Shadowsocks 添砖加瓦,这就是开源的力量,倒下一个 Clowwindy,会有千千万万个 "Clowwindy" 站出来 + +## SSR + +SSR 是 ShadowsocksR 的缩写,网名爱称 “酸酸乳”,是在 Shadowsocks 的作者被请去喝茶之后,网名为 [breakwa11](https://github.com/breakwa11) 的用户发起的 Shadowsocks 的一个分支版本,它在 Shadowsocks 的基础上增加了一些数据混淆方式,修复了部分安全问题并提高了 QoS 优先级。由于 ShadowsocksR 在协议和混淆方面做了改进,更加不容易被 GFW 检测到,而且兼容原 Shadowsocks,并为新项目命名为 Shadowsocks-R,一开始部分代码由社区人员进行更新。由于不完全开源,也导致后来使用 SS 和 SSR 的用户分为两个阵营,互相撕逼,直到开发者 breakwa11 被人肉出来。breakwa11 最终决定删除 Shadowsocks-R 项目的所有代码,并解散了所有相关群组 + +### 事件始末澄清 + +ShadowsocksR 的作者一开始曾有过违反 GPL 协议,在发布二进制文件时不开放源码的争议。不过后来 Shadowsocks-R 项目由 breakwa11 采用了与 Shadowsocks 相同的 GPL、Apache、MIT 等多重自由软件许可协议 + +* 2017-07-19,ShadowsocksR 作者 breakwa11 在 Telegram 频道 ShadowsocksR news 里转发了深圳市启动 SS 协议检测的消息并被大量用户转发,在电报(TG)圈引发恐慌。 +* 2017-07-24,breakwa11 遭到自称 “ESU.TV” 的不明省份人士人身攻击,对方宣传如果不停止开发并阻止用户讨论此事件将发布更多包含个人隐私的资料,随后 breakwa11 表示遭到对方人肉搜索并公开个人资料。为防止对方继续伤害无关人士,breakwa11 删除了 GitHub 上的所有代码、解散相关交流群组,并停止 ShadowsocksR 项目 + +从本质上来说,Shadowsocks 与 ShadowsocksR 的基本原理相同,都是基于 Socks5 的代理工具,只在本地客户端和服务器对数据包加解密,然后使用 Socks5 协议转发加密的数据包,而不用在乎使用什么协议,所以 Socks5 代理比其他应用层代理速度要快的多 + +### Socks5 + +这里顺带科普一下 Socks5,Socks5 代理的原理是把你的网络数据请求先发送到你的代理服务器,然后由代理服务器转发给目标;如果目标有反馈发送到代理服务器,那么代理服务器会将数据包直接传回到你的本地网络,整个过程只需要数据的二次传输,并没有额外的处理。 + +> 示例:现在呢在深圳,你的代理服务器在香港,如果你想要访问 Google,那么你首先需要把数据请求通过本地 Socks5 代理客户端发给在你在香港的服务器上的 Socks5 代理服务端,然后你在香港的服务器将数据请求发送给 Google,再把 Google 反馈的结果传到你香港的代理服务器,然后通过 Socks5 服务端回传到本地的 Socks5 客户端,这样就可以绕开 GFW 的检测而实现科学上网 + +显而易见,Socks5 代理的所有数据走的任然是公网,而且在公网传输过程中,没有对数据进行任何加密和混淆,这跟 VPN 在公网建立虚拟专用通道传输过程中,对数据高强度加密的方式完全不同。Shadowsocks 和 ShadowsocksR 只在客户端和服务器端对数据做了简单加密和认证,主要功能是流量转发,过强才是主要目的。虽然 ShadowsocksR 已经停止更新很久了,而 Shadowsocks 仍处于社区人员的更新和维护之中,不断修复漏洞并增加新功能,所以现在 Shadowsocks 比 ShadowsocksR 更强大 + +### 提醒 + +不要迷信 SSR 一定比 SS 强,也包括现在的 V2Ray,Trojan,甚至是 WireGuard 等,因为增加混淆意味着损失速度,混淆加密越是强悍,那么其速度和稳定性损失就越大,另外 SSR 至今已被研究透了,而且长时间没有更新维护,其流量特征是可以被 GFW 精准识别,所以用 SSR 和 SS 没有本质区别,由于 SS 一直更新维护,反而更稳定。 + +## V2Ray + +[V2Ray](https://v2fly.org) 是在 Shadowsocks 被封杀后,为表示抗议而开发,属于后起之秀,功能更加强大,为抗 GFW 封锁而生。V2Ray 现在已经是 Project V 项目的核心工具。而 Project V 是一个平台,其中也包括支持 Shadowsocks 协议。由于 V2Ray 早于 Project V 项目,且名声更大,我们习惯称 Project V 项目为 V2Ray,所以我们平常所说的 V2Ray 其实就是 Project V 这个平台,也就是一个工具集。其中,只有 VMess 协议是 V2Ray 社区原创的专属加密通讯协议 + +V2Ray 目前支持一下协议(截止 2019-12) + +* Blackhole: 中文名称“黑洞”,是一个出站数据协议,它会阻碍所有的数据的出站,配合路由(Routing)一起使用,可以访问被封杀的网站 +* DNS: 是一个出站协议,主要用于拦截和转发 DNS 查询。此出站协议只能接收 DNS 流量(包含基于 UDP 和 TCP 协议的查询),其它类型的流量会导致错误。在处理 DNS 查询时,此出站协议会将 IP 查询(即 A 和 AAAA)转发给内置的 DNS 服务器。其它类型的查询流量将被转发至原本的目标地址,DNS 出站协议在 V2Ray 4.15 中引入 +* Dokodemo-door: 中文名称“任意门”,是一个入站数据协议,它可以监听一个本地端口,并把所有进入此端口的数据发送到指定服务器的一个端口,从而达到端口映射的效果 +* Freedom: 是一个出站协议,可以用来向任意网络发起(正常的)TCP 或 UDP 数据 +* HTTP: 超文本传输协议,是传统的代理协议 +* Socks: 标准的 Socks 协议实现,兼容 [Socks 4](http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol)、[Socks 4a](http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4A.protocol)、[Socks 5](http://www.socks.nec.com)『🙃目前无法正常访问』,也属于是一种传统的代理协议 +* VMess: 是 V2Ray 专用的加密传输协议,它分为入站和出站两部分,通常作为 V2Ray 客户端和服务器之间的桥梁。因为增加了混淆和加密,据说比 Shadowsocks 更安全。VMess 依赖于系统时间,请确保使用 V2Ray 的系统 UTC 时间误差在 90 秒之内,与时区无关。在 Linux 系统中可以安装 ntp 服务来自动同步系统时间 +* Shadowsocks: 包含入站,出站两部分协议,兼容大部分其它版本的实现。最早被个人开发的科学上网梯子协议,但 V2Ray 目前不支持 ShadowsocksR +* [Trojan](https://trojan-gfw.github.io/trojan/protocol): 特洛伊木马服务器如何对有效的特洛伊木马协议和其他协议(可能是 HTTPS 或任何其他探针)做出反应 +* VLESS: 是一个无状态的轻量传输协议,它分为入站和出站两部分,可以作为 V2Ray 客户端和服务器之间的桥梁,与 VMess 不同,VLESS 不依赖与系统时间,认证方式同样为 UUID,但不需要 alterId +* Loopback: 是一个出站协议,可使出站连接被重新路由,最低支持版本 v4.36.0+ + +截止到 2021-05,V2Ray 可选的传输层配置有:TCP、mKCP,WebSocket、HTTP/2,DomainSocket、QUIC、gRPC。其中 mKCP、QUIC 和 TCP 用于优化网络质量;WebSocket 用于伪装;HTTP/2 和 DomainSocket 用于传输以及 TLS 加密 + +V2Ray 不仅可以在传输层配置 TLS 使用 HTTP 和 Socks 变成 HTTPS 和 Socks over TLS 协议,也可以使用 MTProto、Shadowsocks 和 VMess 通过传输层配置 TLS 加密伪装成 TLS 流量。所以,VMess 配置是 TLS 加密的最常见的做法,但没人会对 Shadowsocks 使用 TLS 加密,因为完全没有意义 + +### 客户端 + +> [V2Ray 客户端](https://v2xtls.org/v2ray%e5%ae%a2%e6%88%b7%e7%ab%af/) + +## Xray 与 XTLS + +Xray 与 V2Ray 完全类同,Xray 是 Project X 项目的核心模块。因为 Xray 和 [XTLS 黑科技](https://github.com/XTLS/) 的作者 [rpfx](https://github.com/rprx) 曾是 V2fly 社区的重要成员,所以 Xray 直接 fork 全部 V2Ray 的功能,然后进行性能优化,并增加了新的功能,使 Xray 在功能上成为了 V2Ray 的超集,且完全兼容 V2Ray。 + +简而言之,Xray 是 V2Ray 的项目分支,Xray 是 V2Ray 的超集,就跟 Trojan-Go 和 Trojan-GFW 的关系类似,而且 Xray 性能更好,速度更快,更新迭代也更频繁。 + +> 由于 V2Ray-core 4.33.0 版本起,删除了 XTLS 黑科技,但任然支持 VLESS,所以是否原生支持 XTLS 是 Xray 和 V2Ray 最大的区别之一 + +## Trojan 与 Trojan-Go + +Trojan 原特指特洛伊木马,是一种计算机病毒程序。但是,我们今天所说的 Trojan 是一种新型的科学上网技术,全称为 Trojan-GFW,是目前最成功的科学上网伪装技术之一。你可以认为 Trojan 是 V2Ray 的 “WS + TLS” 模式的精简版,速度比 V2Ray 更快,伪装比 V2Ray 更逼真,更难以备 GFW 识别 + +Trojan 工作原理:Trojan 通过监听 443 端口,模仿互联网上最常见的 HTTPS 协议,把合法的 Trojan 数据伪装成正常的 HTTPS 通信,并真正地完整完成 TLS 握手,以诱骗 GFW 认为它就是 HTTPS,从而不被识别。Trojan 处理来自外界的 HTTPS 请求,如果是合法的,那么为该请求提供服务,否则将该流量交给 Caddy、Nginx 等 Web 服务器,由 Caddy、Nginx 等为其提供网页访问服务。基于整个交互过程,这样能让你的 VPS 更像一个正常的 Web 服务器,因为 Trojan 的所有行为均为 Caddy、Nginx 等 Web 服务器一致,并没有引入额外特征,从而达到难以识别的效果 + +Trojan-Go 是 Trojan-GFW 的分支项目,对 Trojan 进行性能优化,并增加不少新特性,Trojan-Go 性能和功能均有大幅度的提升,而且支持分流和 CDN + +## 总结 + +通过上述,以及其他信息来源对这些技术有了一定的基本认识,从以下的几个方面来进行总结 + +### 原理不同 + +VPN:强调对公网传输过程中数据的加解密 +SS/SSR/V2Ray/Xray/Traojan:专注于在客户端和服务器间加密,公网传输过程中特征没有 VPN 明显 + +### 目的不同 + +VPN:是走在公网中自建的虚拟专用通道,使用强大的加解密算法,为数据传输安全性、私密性而生,被广泛应用于企业、高校、科研部门等远程数据传输领域 +SS/SSR/V2Ray/Xray/Trojan/Trojan-Go:是为了数据能够安全通过 GFW 而生,更强调的是对数据的混淆和伪装,加解密只是为了更好的隐藏数据特征而顺利通过 GFW 的检测 + +### 项目诞生的大致顺序 + +VPN > SS > SSR/V2Ray/WireGuard > Trojan/Trojan-Go > Xray + +## 致每一个追求真理的人 + +凡事都有两面性,看如何去看待。其实我一直认为,有墙确实是一件好事,毕竟祖国互联网发展也不过 20 多年,在一定程度上过滤掉了一些没有自我认知,自我思考盲目跟风,觉得国外的月亮圆,国外什么都好的一群人;保障了在互联网开始萌芽的本土企业(毕竟国外的产品和技术都已经发展了好几轮,从解决问题的能力,使用的用户体验(不含本地化)等等方面都是完全甩开本地企业的服务)发展,提供给企业和网民一起成长的安全环境,在这一点上 GFW 功不可没。但作为一个技术从业人员来说,其中大部分的技术理念思想,技术平台等纯技术领域相关的东西来说不是那么友好,常常伴随着由于网络原因而造成的各种乱七八糟的问题,对于暂无本土相关替代服务支持时,在一定程度上阻碍了国内相关技术的发展进度和创新。 + +真如前面所述,凡事都有两面性。对于未知的事物,人类本能的会产生恐惧,因为它不可控,而互联网正真不可控的不是技术而是人,人是复杂的个体,一个技术的好坏不是它本身,而是使用的人在做什么样的事,而做的事在一定的环境下它是有好坏之分的。因此我在这里对自我约束,仅为获取相关学习的知识,不参与散布谣言、政治相关等言论,踏踏实实搞技术 + +> 希望未来的有一天,国内技术人员的输出是全球技术的风向标 + +## 参考 + +1. [试用 Always Free 云服务](https://www.oracle.com/cn/cloud/free/) +2. [申请 Oracle Cloud 永久免费服务](https://www.daniao.org/6537.html) +3. [2021年申请永久免费甲骨文云 Oracle Cloud 并创建实例最全攻略](https://zhuanlan.zhihu.com/p/352736372) +4. [多种科学上网指导教程汇总](https://merlinblog.xyz/wikipageguide.html) +5. [V2ray XTLS黑科技](https://v2xtls.org) diff --git a/source/_posts/fuck-gfw.md b/source/_posts/fuck-gfw.md new file mode 100644 index 000000000..0b24138cf --- /dev/null +++ b/source/_posts/fuck-gfw.md @@ -0,0 +1,474 @@ +--- +title: 专治各种网络不服 +date: 2020-02-27 15:00:10 +categories: DevTool +tag: [Exp] +--- + +众所周知国内的开发已经由原来的“致敬”到现在软件生态领域的“引领”(目前来说还未到真正引领)世界技术发展,但是一个问题始终还是未能有所突破,作为中国的开发者每次在面对新的技术在环境搭建就劝退了一众人,很多开发所依赖的项目资源都来自国外服务,而由于中国的特殊,对很多国外服务的限制,让你在开始的第一阶段总是碰的鼻青脸肿,把大量的时间浪费在环境搭建的等待上,虽然现在很多开源组织或者一线大厂提供了相应的镜像服务方便国内的开发者,但很多都还需要我们自行去更改或者解决这些问题,本篇文章就是我的开发之路上的各种网络问题的解决办法 + + + +## Gradle + +Gradle 作为新一代的包管理工具,早期作为 Android 项目的御用包管理,渐渐的越来越多的服务端项目也开始在使用 Gradle 来进行管理,至于 Gradle 和 Maven 的对比,我这里不做评论,请移步 Gradle 官方网站对两个包管理的比较 [Gradle vs Maven Comparison](https://gradle.org/maven-vs-gradle/) + +> Gradle 镜像地址:https://services.gradle.org/distributions +> Gradle 腾讯镜像:https://mirrors.cloud.tencent.com/gradle + +### 单项目配置 + +#### Android 项目 + +在 Android 项目中主要有下面这些配置文件 + +* build.gradle + * 项目级别:在项目根目录,定义项目中所有模块共用的 Gradle 代码库和依赖项 + * 模块级别:在模块根目录,用于为其所在的特定模块配置构建设置,可以通过配置这些构建设置提供自定义打包选项(如额外的构建类型和产品变种),以及替换 main/ 应用清单或顶层 build.gradle 文件中的设置 +* settings.gradle:项目的根目录下,用于指示 Gradle 在构建应用时应将哪些模块包含在内 +* xxxxx.gradle:模块配置文件,可将冗长的配置信息分块进行配置,比如依赖的版本统一管理等 + +>更详细的介绍请查看 Android 官方说明 [配置构建](https://developer.android.google.cn/studio/build) + +项目的依赖仓库镜像配置,在项目级别的 build.gradle 文件中,如下进行镜像的指定示例 + +```groovy +buildscript { + repositories { + // 添加或修改这里指向的镜像仓库地址,默认使用,https://maven.google.com/ + google() + // 如果需要指定 google 的镜像地址,可注释掉上面的 google()默认配置,使用下面的显示指定配置 + // google{ + // url 'https://maven.aliyun.com/repository/google' + // } + jcenter() + maven { + // 比如修改这里的镜像指向地址 + // url 'https://jitpack.io' + url 'https://maven.aliyun.com/repository/public' + } + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + // 添加或修改这里指向的镜像仓库地址,默认使用,https://maven.google.com/ + google() + jcenter() + maven { + // 比如修改这里的镜像指向地址 + // url 'https://jitpack.io' + url 'https://maven.aliyun.com/repository/public' + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} +``` + +>我们所依赖的 jar 文件,阿里云镜像中不一定都有,所以我们需要根据具体的项目实际情况调整 + +#### SpringBoot 项目 + +在 SpringBoot 项目中主要有下面这些配置文件 + +* build.gradle:主要配置文件,关于项目的依赖关系主要在该文件中配置 +* settings.gradle:项目信息文件,项目的一些可在这里配置,比如项目名称、子项目信息 +* xxxxx.gradle:模块配置文件,可将冗长的配置信息分块进行配置,比如依赖的版本统一管理等 + +如下,build.gradle 配置信息 + +```groovy +plugins { + id 'org.springframework.boot' version '2.1.6.RELEASE' + id 'io.spring.dependency-management' version '1.0.8.RELEASE' + id 'java' +} + +group = 'org.incoder' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = 1.8 +sourceCompatibility = 1.8 + +repositories { + // 显式指定仓库访问地址,以下两个地址推荐阿里云镜像,按顺序执行 + maven { + // 指向 阿里云 镜像仓库 + url 'https://maven.aliyun.com/repository/public' + } +// maven { +// // 指向 spring 官方仓库 +// url 'http://repo.spring.io/release' +// } + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-aop' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + } +} + +test { + useJUnitPlatform() +} +``` + +对于 SpringBoot 项目,不管是全局镜像配置还是单项目的镜像配置,可能存在 SpringBoot 依赖的插件依赖无法下载,通常表现为卡在 `Gradle: Download org.springframework.boot.gradle.plugin-xxxx.pom` 这里,那则需要也配置插件镜像,在 settings.gradle 文件**最上面**加入如下配置 + +```groovy +pluginManagement { + repositories { + maven { url "https://maven.aliyun.com/repository/gradle-plugin" } + } +} +``` + +### 全局配置 + +在当前系统 `${USER_HOME}/.gradle/` 目录下创建 `init.gradle` 文件,将 Maven 和 Jcenter 仓库都指向阿里云镜像仓库 + +```groovy +`// 如果你的 springboot plugin 下载也很慢,也可以全局设置插件下载地址` +pluginManagement { + repositories { + maven { url "https://maven.aliyun.com/repository/gradle-plugin" } + } +} +// 项目依赖第三方包下载地址替换 +allprojects{ + repositories { + + def ALIYUN_CENTRAL_URL = 'https://maven.aliyun.com/repository/central/' + def ALIYUN_JCENTER_PUBLIC_URL = 'https://maven.aliyun.com/repository/public/' + def ALIYUN_GOOGLE_URL = 'https://maven.aliyun.com/repository/google/' + def ALIYUN_GRADLE_PLUGIN_URL = 'https://maven.aliyun.com/repository/gradle-plugin/' + def ALIYUN_SPRING_URL = 'https://maven.aliyun.com/repository/spring/' + def ALIYUN_SPRING_PLUGIN_URL = 'https://maven.aliyun.com/repository/spring-plugin/' + def ALIYUN_GRAILS_CORE_URL = 'https://maven.aliyun.com/repository/grails-core/' + def ALIYUN_APACHE_SNAPSHOTS_URL = 'https://maven.aliyun.com/repository/apache-snapshots/' + + all { ArtifactRepository repo -> + if(repo instanceof MavenArtifactRepository){ + def url = repo.url.toString() + // central + if (url.startsWith('https://repo1.maven.org/maven2/')) { + project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_CENTRAL_URL." + remove repo + } + // jcenter + if (url.startsWith('https://jcenter.bintray.com/') || url.startsWith('http://jcenter.bintray.com/')) { + project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_JCENTER_PUBLIC_URL." + remove repo + } + // google + if (url.startsWith('https://maven.google.com/')) { + project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_GOOGLE_URL." + remove repo + } + // gradle-plugin + if (url.startsWith('https://plugins.gradle.org/m2/')) { + project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_GRADLE_PLUGIN_URL." + remove repo + } + // spring + if (url.startsWith('https://repo.spring.io/libs-milestone/')) { + project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_SPRING_URL." + remove repo + } + // spring-plugin + if (url.startsWith('http://repo.spring.io/plugins-release/') || url.startsWith('https://repo.spring.io/plugins-release/')) { + project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_SPRING_PLUGIN_URL." + remove repo + } + // grails-core + if (url.startsWith('https://repo.grails.org/grails/core/')) { + project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_GRAILS_CORE_URL." + remove repo + } + // apache snapshots + if (url.startsWith('https://repository.apache.org/snapshots/')) { + project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_APACHE_SNAPSHOTS_URL." + remove repo + } + } + } + + maven { url ALIYUN_CENTRAL_URL } + maven { url ALIYUN_JCENTER_PUBLIC_URL } + maven { url ALIYUN_GOOGLE_URL } + maven { url ALIYUN_GRADLE_PLUGIN_URL } + maven { url ALIYUN_SPRING_URL } + maven { url ALIYUN_SPRING_PLUGIN_URL } + maven { url ALIYUN_GRAILS_CORE_URL } + maven { url ALIYUN_APACHE_SNAPSHOTS_URL } + } +} +``` + +## Maven + +### 单项目配置 + +项目配置文件`pom.xml`文件中配置镜像地址,标签下添加标签配置阿里云镜像 + +```xml + + + alimaven + aliyun maven + https://maven.aliyun.com/repository/public + + +``` + +### 全局配置 + +Maven 默认配置文件地址,`Users//.m2`目录下,如果没有,则新建一个`settings.xml`文件,进行镜像的配置 + +```xml + + + + + + aliyunmaven + * + 阿里云central仓库 + https://maven.aliyun.com/repository/central/ + + + aliyunmaven + * + 阿里云public仓库 + https://maven.aliyun.com/repository/public/ + + + aliyunmaven + * + 阿里云Google仓库 + https://maven.aliyun.com/repository/google/ + + + aliyunmaven + * + 阿里云gradle-plugin仓库 + https://maven.aliyun.com/repository/gradle-plugin/ + + + aliyunmaven + * + 阿里云Spring仓库 + https://maven.aliyun.com/repository/spring + + + aliyunmaven + * + 阿里云spring-plugin仓库 + https://maven.aliyun.com/repository/spring-plugin/ + + + aliyunmaven + * + 阿里云grails-core插件仓库 + https://maven.aliyun.com/repository/grails-core/ + + + aliyunmaven + * + 阿里云Apache仓库 + https://maven.aliyun.com/repository/apache-snapshots/ + + + + + + + +``` + +>具体的配置教程,可参考[阿里云云效 maven](https://maven.aliyun.com/mvn/guide) + +## Homebrew + +Homebrew是 macOS 系统的一款开源的包管理器 + +### 修改镜像 + +```bash +# macOS 系统修改 homebrew 镜像地址为清华镜像 +git -C "$(brew --repo)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git +git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git +git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git +# 『可选』 如果之前有安装过 font 相关,则更改 font 相关的镜像地址,否则会提示 cask-fonts 不存在 +git -C "$(brew --repo homebrew/cask-fonts)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-fonts.git +# 『可选』 如果之前有安装过 drivers 相关,则更改 drivers 相关的镜像地址,否则会提示 cask-drivers 不存在 +git -C "$(brew --repo homebrew/cask-drivers)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-drivers.git +# 更换后测试工作是否正常 +brew update +# 替换Homebrew Bottles源(以下方式二选一即可) +# 1. bash用户 +echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles' >> ~/.bash_profile +source ~/.bash_profile +# 2. zsh用户 +echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles' >> ~/.zshrc +source ~/.zshrc +``` + +### 恢复镜像 + +```bash +# macOS 系统恢复 homebrew 原镜像地址 +git -C "$(brew --repo)" remote set-url origin https://github.com/Homebrew/brew.git +git -C "$(brew --repo homebrew/core)" remote set-url origin https://github.com/Homebrew/homebrew-core.git +git -C "$(brew --repo homebrew/cask)" remote set-url origin https://github.com/Homebrew/homebrew-cask.git +# 『可选』 如果之前有安装过 font 相关,则更改 font 相关的镜像地址,否则会提示 cask-fonts 不存在 +git -C "$(brew --repo homebrew/cask-fonts)" remote set-url origin https://github.com/Homebrew/homebrew-cask-fonts.git +# 『可选』 如果之前有安装过 drivers 相关,则更改 drivers 相关的镜像地址,否则会提示 cask-drivers 不存在 +git -C "$(brew --repo homebrew/cask-drivers)" remote set-url origin https://github.com/Homebrew/homebrew-cask-drivers.git +# 更换后测试工作是否正常 +brew update +# 取消 Homebrew Bottles源设置(以下方式二选一) +# 1. bash用户,取消 HOMEBREW_BOTTLE_DOMAIN 设置 +vim ~/.bash_profile +source ~/.bash_profile +# 2. zsh用户,取消 HOMEBREW_BOTTLE_DOMAIN 设置 +vim ~/.zshrc +source ~/.zshrc +``` + +>其他镜像:[中科大](https://mirrors.ustc.edu.cn/) + +## Github + +### 资源文件无法加载 + +GitHub 上各种图片都无法加载,不仅仅是头像,包括各个仓库中的图片也是。开始以为是科学上网的问题,又或者是图片本身失效,可是验证下来都是无关的,经过一番折腾查找,原来是 DNS 被污染导致 + +解决方法:通过查看头像等文件的访问地址,了解到这些地址的域名都是 `githubusercontent.com`,然后通过[IP 地址查询](https://www.ipaddress.com)可以找到其对应的 IP 地址,并将其相关二级域名一起配置到 Hosts 文件中 + +host 路径 + +* macOS:/etc/ +* Windows:C:\Windows\System32\drivers\etc + +```bash +# Github start +140.82.114.3 github.com +140.82.112.4 gist.github.com + +185.199.108.153 assets-cdn.github.com +185.199.109.153 assets-cdn.github.com +185.199.110.153 assets-cdn.github.com +185.199.111.153 assets-cdn.github.com +# *.githubusercontent.com raw|gist|cloud|camo|avatars0-9|avatars +199.232.96.133 raw.githubusercontent.com +199.232.96.133 gist.githubusercontent.com +199.232.96.133 cloud.githubusercontent.com +199.232.96.133 camo.githubusercontent.com +199.232.96.133 avatars0.githubusercontent.com +199.232.96.133 avatars1.githubusercontent.com +199.232.96.133 avatars2.githubusercontent.com +199.232.96.133 avatars3.githubusercontent.com +199.232.96.133 avatars4.githubusercontent.com +199.232.96.133 avatars5.githubusercontent.com +199.232.96.133 avatars6.githubusercontent.com +199.232.96.133 avatars7.githubusercontent.com +199.232.96.133 avatars8.githubusercontent.com +199.232.96.133 avatars.githubusercontent.com +``` + +### git clone 慢的想砸电脑 + +#### 方式一:设置代理 + +这个无解,只能在开启代理的前提下,也给终端设置代理 + +```bash +# 只对 github.com,这里的 port 端口,需要你本地HTTP代理的端口来设置 +git config --global http.https://github.com.proxy socks5://127.0.0.1:port +# 取消代理 +git config --global --unset http.https://github.com.proxy +``` + +#### 方式二:Gitee 中转 + +另一种方式,适用于你需要获取 GitHub 源码做相关的其他操作时,可以借助于 Gitee 来作为中转 + +![improt-github-to-gitee](https://res.cloudinary.com/incoder/image/upload/v1585097262/blog/improt-github-to-gitee.png) + +可以参考大佬的手把手教你 + + +#### 方式三:cnpmjs镜像中转 + +在你需要 clone 的仓库地址中,添加 `.cnpmjs.org` 在 `github.com` 的后面 + +![github-cnpmjs](https://res.cloudinary.com/incoder/image/upload/v1595934447/blog/github-cnpmjs.png) + +#### 方法四:jsdelivr 免费 CDN 加速 + +适用于下载单个文件 + + + +## yum + +yum 源配置文件路径:/etc/yum.repo s.d/ + +```bash +# 查看配置,Centos-xx.repo 类型的文件即为源 +cd /etc/yum.repos.d && ls +# 备份源,创建 centos.back 文件夹,并将 *.repo 类型文件剪切到 centos.back 文件夹 +mkdir centos.back && mv *.repo centos.back +# 下载安装国内源文件,分别下载了阿里和 163 的源文件 +wget http://mirrors.aliyun.com/repo/Centos-7.repo +wget http://mirrors.163.com/.help/CentOS7-Base-163.repo +# 清空缓存 +yum clean all +# 生成缓存 +yum makecache +``` + +## docker + +docker 镜像源默认是使用的 docker hub(https://hub.docker.com) 的源,为加快效率,我们通常也将镜像源切换到国内镜像 + +```bash +# 修改 docker 源配置文件 +vim /etc/docker/daemon.json +# 内容如下,分别是 docker 中国官方镜像,163,中国科学技术大学 +{ + "registry-mirrors": [ + "https://registry.docker-cn.com", + "https://hub-mirror.c.163.com", + "https://ustc-edu-cn.mirror.aliyuncs.com" + ] +} +# 保存 daemon.json 文件修改,并退出 +:wq +# 重启 daemon 服务 +sudo systemctl daemon-reload +# 重启 docker 服务 +sudo systemctl restart docker +``` + +## 参考 + +* [Homebrew/Linuxbrew 镜像使用帮助](https://mirror.tuna.tsinghua.edu.cn/help/homebrew/) +* [阿里云公共代理库配置指南](https://help.aliyun.com/document_detail/102512.html?spm=a2c40.aliyun_maven_repo.0.0.36183054ncuCr4) \ No newline at end of file diff --git a/source/_posts/gdd-2019.md b/source/_posts/gdd-2019.md new file mode 100644 index 000000000..298268486 --- /dev/null +++ b/source/_posts/gdd-2019.md @@ -0,0 +1,95 @@ +--- +title: Google Developer Days 2019 +date: 2019-09-12 10:43:46 +categories: Google +tag: [GDD] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1568675354/blog/google-developer-days.jpg) + +连续三年申请参加 Google Developer Days,今年终于中签了,而且和好友[大蛇丸](https://ceaser.wang)及公司同事同时中签(可能是我们都使用了忍术)。嗯,终于离404公司又进了一步,哈哈哈~ +废话不啰嗦了,这篇文章就唠唠参加 GDD 的前前后后。 + + + +众所周知每年 Google 会在 5 月份上旬在美国举行 Google 1/O (全球开发者大会)大会,在大会上无例外的推出新版本的 Android 系统(虽然只是 bate版本,今年 Android 取消了过去使用甜品命名的方式,而直接采用阿拉伯数字命名)等等软硬件上的技术探索和研究成果,给 Android 领域确立风向标。 + +而在中国,大概每年 9 月份会在中国上海举办 Google Developer Days,今年是第 4 年,可见 Google 对中国市场的重视。 + +* [2019](https://events.google.cn/intl/zh-CN/developerdays2019/) +* [2018](https://www.google.cn/intl/zh-CN/events/developerdays2018/) +* [2017](https://www.google.cn/intl/zh-CN/events/developerdays2017china/) +* [2016](https://www.google.cn/intl/zh-CN/events/developerday2016/) + +## 申请 + +由于 GDD 是不收取门票的,因此会对申请用户进行筛选,这个就要看运气了,可以通过以下的渠道获取信息,进行申请 +* 微信公众号 + ![](https://res.cloudinary.com/incoder/image/upload/v1568547420/blog/google-developers.gif) +* [微博 Google开发者](https://weibo.com/GoogleDevelopers) +* [知乎 谷歌开发者](https://www.zhihu.com/org/google-gu-ge) +* [社区 GDG](https://chinagdg.org) +* 其他渠道 + +申请时需要填写一些资料,如实填写即可,剩下就是静待消息,如果审核通过,会发送邮件/短信通知你,不同的同学接收到的时间可能不同,具体的截止时间,以官方通知为准,没有通过的可查看官方合作的直播平台进行直播观看 + +>众所周知,参加大会的基本是清一色的男同学,因此今年 Google 还专门有为女同学们提供了 1000 名的直通车,具体请移步[官方公众号](https://mp.weixin.qq.com/s/SWMy2pui7j2RMZCcA4bS5A) + +## 参加 + +筛选通过后,那就是自己安排好自己的工作或者是学习,因为大会时间不一定是周末,以及安排好你的行程和住宿(两天的午餐都是由 GDD 提供)。我在杭州,因此就搭乘动车当天早上抵达上海虹桥,换乘地铁抵达目的地(上海世博中心)。由于支付宝并不支持上海地铁,因此需要提前下载一款 "Metro 大都会"应用 + +## 感受 + +满满当当两天下来,收货不少,这一届可以通过官方日程看出,重点是 Flutter 以及 TensorFlow 相关,大部分内容都是偏大前端这个领域,不管是相关应用场景的尝试还是一些技术细节和技术的巧妙实现,都能看得出 Google 在技术领域的话语权,其中有两个技术探索以及一场《挖掘事业发展潜力 - 开拓自己的道路》课堂,各位老师对职业发展讲解让我印象深刻 + +* 与 AR 相结合的 AR 导航(与滴滴合作),解决室内定位问题 +* 与艺术(音乐)结合,让技术有了温度,通过深度学习 +* 开拓自己的道路 + * 对自己的专业技能需要达到融会贯通 + * 要主动的心态去工作,有企业家的精神 + * enjoy 的方式去对待自己所做的决定 + * 只有自己了解自己,才能将自己的推向更高的舞台 + +另外通过现场感受,可以看到活动的现场屏幕边框元素是[Material Desing](https://material.io)中的,三角,圆,矩形,线条,和现场灯光融为一体,每一个视频动画都看得出他们在背后的付出,每一段音乐都那么的契合场景,这是我参加众多线下交流会,在现场感受最深的一次 + +## 其他 + +我们来看一看来自官方的活动精彩瞬间 + + + +### 如何提高中签率 + +在知识星球中,看到有人分享 + +“简单说下对筛选的看法吧,报名的问卷非常的简单,都是一些有没有使用谷歌服务的选项。作为主办方,怎么样才能快速高效的在这之中找到自己想要的人呢? + +这其实就是如何帮助谷歌建立你的**用户画像**,如果谷歌能找到更多的有利的信息,那么成功报名的机率自然会高。 + +那如何做到这点呢?其实很简单,提供使用谷歌服务频率最高最深的邮箱,因为谷歌可以很方便的获取到想要的信息!” + +### 如何回顾 + +错过了现场参与,和视频直播,还能不能观看,答案是当然可以,官方会对直播视频进行剪辑,发布到[bilibili](https://space.bilibili.com/64169458/)视频网站,你可以关注[Google中国](https://space.bilibili.com/64169458) 官方账号方便你第一时间活动更新动态,截止目前为止已发布,随后发布的我会及时更新 + +* [谷歌开发者大会开幕主旨演讲](https://www.bilibili.com/video/av67946527) + +* 移动端 + * [Android 开发最新技术概览](https://www.bilibili.com/video/av68058096) + * [Android 10 和隐私保护:使您的应用顺应变更](https://www.bilibili.com/video/av68061328) + * [Android 无障碍:服务所有人](https://www.bilibili.com/video/av68066152) + * [利用 Kotlin 进行 Android 开发](https://www.bilibili.com/video/av68058669) + * [如何组装你的 Jetpack](https://www.bilibili.com/video/av68059087) + * [CameraX:面向开发者的摄像头支持库](https://bilibili.com/video/av68046760) + * [移动Web技术拓展无限商机](https://www.bilibili.com/video/av67907735) + * [AdMob 广告政策和工具](https://www.bilibili.com/video/av67905866) + * [用谷歌的新数据技术挖掘 App 变现潜力](https://www.bilibili.com/video/av67854284) + * [ConstraintLayout + MotionLayout:打造丰富界面并为其制作动画效果](https://www.bilibili.com/video/av68048631) + * [Material Theming:利用 Material 组件以极具表现力的方式构建主题背景](https://www.bilibili.com/video/av68049492) + * [利用 Material Design 设计深色主题背景](https://www.bilibili.com/video/av68050301) + +* 机器学习 + * [机器学习简介](https://www.bilibili.com/video/av68057077) + * [机器学习赋能智慧营销,成就商业新增长](https://www.bilibili.com/video/av67903202) + * [利用基准化分析和剖析功能提升应用性能](https://www.bilibili.com/video/av68051201) diff --git a/source/_posts/git-account.md b/source/_posts/git-account.md new file mode 100644 index 000000000..e4f4da89b --- /dev/null +++ b/source/_posts/git-account.md @@ -0,0 +1,80 @@ +--- +title: Git 多账号 +date: 2018-10-06 10:54:50 +categories: Git +tag: [git account] +--- + +以前,git的账号只用来在Github上操作,随着积累Git管理的项目不仅仅只来自Github,还有一些其它Git项目托管的平台,例如:[Bitbucket](https://bitbucket.org),[Coding](https://coding.net),[Gitee](https://gitee.com),[Gitlib](https://gitlab.com),以及公司内Git仓库 + +不同的托管平台有着不同的Git账号,无法用一个账号来管理其它的仓库,而且由于不同的托管平台账号不同,因此需要添加不同账号的公钥,这样我们再能在对应平台用对应的账号进行操作 + + + +## 环境 + +* Windows 10 x64 +* Git version 2.16.0 + +> 这里Git的安装不在赘述 + +## 生成对应账号的密钥 + +```sh +# 进入到`your_pc_name/.ssh`, +cd .ssh +# Jerry.x@outlook.com 是我的Github的邮箱,这里需要替换成自己的邮箱 +ssh-keygen -t rsa -C "Jerry.x@outlook.com" +# 命名文件名称或指定文件存放路径等 +# 其它可以回车键进行确认,进行下一步 +``` + +![git-account](https://res.cloudinary.com/incoder/image/upload/v1538887180/blog/git-account.png) + +>完成后,将会生成 `id_rsa_company.pub`(存放公钥)与 `id_rsa_company`(存放私钥)两个文件 + +## 添加公钥到托管平台 + +* 在 `.ssh` 路径下,用文本编辑器打开 `id_rsa_company.pub` 文件,复制内容 +* 在托管平台上添加 ssh public key + +以下以 GitHub 添加为例,其它平台类似 +![git-add-key](https://res.cloudinary.com/incoder/image/upload/v1538887180/blog/git-add-key.png) + +## 添加配置文件 + +在 `.ssh` 路径下,创建 `config` 文件,无文件后缀名,如下示例 + +```sh +# 配置github.com +Host github.com + HostName github.com + IdentityFile C:\\Users\\Jerry\\.ssh\\id_rsa + PreferredAuthentications publickey + User BladeCode + +# 配置 company.domain.com +Host company.domain.com + HostName company.domain.com + IdentityFile C:\\Users\\Jerry\\.ssh\\id_rsa_company + PreferredAuthentications publickey + User Jerry xu +``` + +* `Host`:的名字可以取为自己喜欢的名字 +* `HostName`:这个是真实的域名地址 + 例如:https://github.com/BladeCode/BladeCode.github.io.git,红色标注字段 +* `IdentityFile`:这里是id_rsa的地址 +* `PreferredAuthentications`:配置登录时用什么权限认证 + 可设为publickey,password publickey,keyboard-interactive 等 +* `User`:配置使用用户名 + +## 测试 + +```sh +ssh -T git@github.com +``` + +![git-test](https://res.cloudinary.com/incoder/image/upload/v1538887180/blog/git-test.png) + +> git@github.com,github.com 就是上一步中 `config` 文件中配置的 `HostName` 字段内容 diff --git a/source/_posts/git-bash.md b/source/_posts/git-bash.md new file mode 100644 index 000000000..2a84095fc --- /dev/null +++ b/source/_posts/git-bash.md @@ -0,0 +1,184 @@ +--- +title: Git 常用命令 +date: 2018-10-07 12:43:50 +categories: Git +tag: [git bash] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1586250215/blog/git.png) + + + +记录 Git 日常操作常用命令 + +## git config + +Git级别:system(系统所有用户) < global(当前用户) < local(当前仓库) +* 查看配置信息 + ``` + # 查看对应 Git 级别(--local;--global;--system)的配置信息 + git config --list --local + ``` + +* 新增或修改 + ```sh + git config --global user.name xxxxx + git config --global user.email xxx@xxxx.com + ``` +* 删除用户配置信息 + ```sh + # 如果当前只有一个用户,就不用加入xxxx + git config --global --unset user.name xxxx + ``` + +## git init + +1. 把已有项目代码纳入 Git 管理 + ```sh + # 进入项目根路径 + cd project_dir + # 进行项目 Git 初始化 + git init + ``` +2. 新建项目直接使用 Git 管理 + ```sh + # 在当前路径下创建项目并使用 Git 初始化项目 + git init project_name + # 进入项目根路径 + cd project_name + ``` + +## git clone + +* clone + ```sh + git clone url + ``` +* clone 指定分支 + ```sh + git clone -b branch_name url + ``` +* clone 指定tag + ```sh + # clone + git clone url + # checkout tag + git checkout tag_name + ``` +* clone 指定commit + ```sh + # 查看git commit 历史的 + git log + # 指定 commit SHA + git clone commit_sha_value + ``` + +## git commit + +```sh +git commit -m "注释" +``` +> `-a` 指定标签名,`-m` 指定说明文字 + +## git branch + +* 创建分支 + ```sh + # 创建分支 + git branch branch_name + # 创建并切换到新分支 + git checkout -b branch_name + ``` +* 切换分支 + ```sh + git checkout branch_name + ``` +* 删除分支 + ```sh + # 删除本地分支 + git branch -d branch_name + # 删除远程指定分支 + git push origin --delete branch_name + ``` +* 重命名分支 + ```sh + git branch -m old_branch_name new_branch_name + ``` +* 查看分支 + ```sh + # 查看本地所有分支 + git branch + # 查看远程所有分支 + git branch -r + # 查看本地和远程所有分支 + git branch -a + ``` + +## git tag + +* 新增 tag + ```sh + git tag -a tag_name -m "注释" + ``` +* 查看 tag + ```sh + # 查看指定 tag 信息 + git show tagname + # 查看所有 tag + git tag -l + ``` +* 删除 tag + ```sh + # 删除本地tag + git tag -d tag_name + # 删除远程指定tag + git push origin --delete tag tag_name + ``` +* 推送 tag 到远程 + ```sh + # push 单个 tag + git push origin tag_name + # push 所有 tag + git push [origin] --tags + ``` + +## git mv + +* 重命名文件 + ```sh + git mv old_file_name new_file_name + ``` + +## git log +* 查看仓库 commit 历史日志 + ```sh + # 下面参数可任意组合 + git log --oneline(简洁查看) --all(所有分支) -n4(最近 4 次记录) --graph(图形化展示) + ``` + +## git help + +更多命令 + ```sh + git --help + ``` + +## git other + +* 查看当前项目远程仓库地址 + ```sh + git remote -v + ``` +* 修改仓库地址 + ```sh + # 方式一:直接修改 + git remote set-url origin [url] + # 方式二:先删后加 + git remote rm origin + git remote add origin [url] + # 方式三:直接修改config文件 + ``` + +## 附录 + +* [Git Docs](https://git-scm.com/docs) diff --git a/source/_posts/git-signature.md b/source/_posts/git-signature.md new file mode 100644 index 000000000..34fc1fb8c --- /dev/null +++ b/source/_posts/git-signature.md @@ -0,0 +1,302 @@ +--- +title: Git 签名 +date: 2024-06-17 22:30:50 +categories: Git +tag: [git signature] +--- + +![verified-commit](https://docs.github.com/assets/cb-17614/mw-1440/images/help/commits/verified-commit.webp) + +通常 Push 代码到远程托管平台(GitHub,Gitlab,Gitee 等),需要提前在托管平台上传我们 Git 账户的公钥(*.pub),平台使用上传的公钥来验证身份(本地的 Git 私钥与平台上的公钥配对,以确保你有权限读写该仓库),该验证只会在 Push 时进行检查 + + + +## 为什么要签名 + +虽然对 Push 做了检查,但依然不够安全,因为任何拥有该仓库权限的人,都可以在 commit/tag 时使用 `git config user.name "假的用户名"`, `git config user.email "假的邮箱地址"` 命令来伪造提交者用户信息,这样我们根本无法追溯提交者的身份,所以我们需要给 commit/tag 签名,签名的目的是确保提交的代码在传输和存储过程中没有被篡改,并验证提交者的身份,同时也保证代码的可追溯性 + +commit 签名是在本地,在使用 `git commit` 命令时进行签名,push 时会将你的签名信息,原封不动 push 到远程仓库 + +commit 签名只是用于验证这条 commit 来自于你本人,与是否有权限操作远程仓库无关 + +## 如何签名 + +可以使用 GPG、SSH 或 S/MIME,可以在本地对 commit/tag 进行签名。 这些 commit/tag 在 GitHub 上标示为已验证,便于其他人信任更改来自可信的来源 + +### SSH、GPG、S/MIME 区别 + +- SSH 签名是最容易生成的,甚至可以将现有身份验证密钥上传到 GitHub 以用作签名密钥 +- 生成 GPG 签名比生成 SSH 密钥复杂,但 GPG 具有 SSH 没有的功能,GPG 密钥可以在不使用时过期或撤销 +- 较大型组织的环境中通常需要 S/MIME 签名 + +### 👍 SSH(常用) + +> SSH 签名验证需 Git 2.3.4 及以上版本 + +#### 检查现有 SSH 密钥 + +```bash +# 查看本地 ~/.ssh 路径现有密钥 +ls -al ~/.ssh +# 检查输出的列表 +``` + +检查已有密钥列表已有 RSA 密钥,如果你已经有 **SHA-2 算法生成 RSA 密钥** 那么你可以 跳过 `生产新 SSH 密钥`,如果没有,为了安全,还是建议重新生成 SHA-2 算法生成 RSA 密钥 + +#### 生产新 SSH 密钥 + +{% note info %} + +1. 2022.03.15 + - 在该日期 GitHub 删除旧的、不安全的密钥类型来提高安全性 + - 自该日期起,不再支持 **DSA 密钥** (ssh-dss)。 无法 在 github.com 上向个人帐户添加新的 **DSA 密钥** +2. 2021.11.02 + - 在该日期 **之前** 带有 valid_after 的 **RSA 密钥** (ssh-rsa) 可以继续使用任何签名算法 + - 在该日期 **之后** 生成的 RSA 密钥 必须 使用 **SHA-2 签名算法**。 一些较旧的客户端可能需要升级才能使用 SHA-2 签名 + +{% endnote %} + +在生成 SSH 密钥时,我们可以给 SSH 密钥添加密码,也可以不添加密码,这里根据需要选择是否添加密码 + +![git-ssh-keygen](https://res.cloudinary.com/incoder/image/upload/v1722773384/blog/git-ssh-keygen.png) + +1. 执行 `ssh-keygen -t ed25519 -C "Jerry.x@outlook.com"` 命令,这里的邮箱换成你的 GitHub 邮箱 +2. 确认密钥存放位置 + - 如果不需调整(默认:`/User/blade/.ssh/id_ed25519`),可 Enter 键进入下一步 + - 这里我重命名了密钥 `/User/blade/.ssh/id_ed25519_test` +3. 给密钥设置密码 + - 如果无需设置,可 Enter 键进入下一步 + - 如果需设置,输入密码即可 +4. 确认输入的密钥密码,和上一步输入内容一致 +5. 提示生成的密钥存放位置和指纹信息 + +{% note primary 如果密钥 **设置了密码**,需要将 SSH 密钥添加到 ssh-agent %} + +在向 ssh-agent 添加新的 SSH 密钥管理密钥前,应该检查现有 SSH 密钥并生成新的 SSH 密钥 + +{% tabs Sixth unique name %} + + + +> 如果已安装 [GitHub Desktop](https://github.com/apps/desktop),可使用它克隆存储库,而无需处理 SSH 密钥 + +1. 在新的“管理员提升”__ PowerShell 窗口中,确保 ssh-agent 正在运行。 可以使用“使用 SSH 密钥密码”中的“自动启动 ssh agent”说明,或者手动启动它 + + ```bash + # start the ssh-agent in the background + Get-Service -Name ssh-agent | Set-Service -StartupType Manual + Start-Service ssh-agent + ``` + +2. 在无提升权限的终端窗口中,将 SSH 私钥添加到 ssh-agent。 如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 id_ed25519 替换为私钥文件的名称 + + ```bash + ssh-add c:/Users/YOU/.ssh/id_ed25519 + ``` + + + + + +> 将 SSH 密钥添加到该代理时,应使用默认的 macOS ssh-add 命令,而不是使用通过 macports、homebrew 或某些其他外部来源安装的应用程序 + +1. 在后台启动 ssh 代理 + + ```bash + $ eval "$(ssh-agent -s)" + > Agent pid 59566 + ``` + + 根据您的环境,您可能需要使用不同的命令。 例如,在启动 `ssh-agent` 之前,你可能需要通过运行 `sudo -s -H` 根访问,或者可能需要使用 `exec ssh-agent bash` 或 `exec ssh-agent zsh` 运行 `ssh-agent` + +2. 如果你使用的是 macOS Sierra **10.12.2** 或更高版本,则需要修改 `~/.ssh/config` 文件以自动将密钥加载到 `ssh-agent` 中并在密钥链中存储密码 + - 检查你的 `~/.ssh/config` 文件是否在默认位置 + - 如果文件不存在,请创建该文件 + - 打开你的 `~/.ssh/config` 文件,然后修改文件以包含以下行。 如果您的 SSH 密钥文件与示例代码具有不同的名称或路径,请修改文件名或路径以匹配您当前的设置 + + ```bash + Host github.com + # 如果看到了 Bad configuration option: usekeychain 错误, + # 取消下一行注释,使用 IgnoreUnknown 配置 + # IgnoreUnknown UseKeychain + AddKeysToAgent yes + # 如果你选择不向密钥添加密码,应该省略 UseKeychain 行 + UseKeychain yes + IdentityFile ~/.ssh/id_ed25519 + ``` + +3. 将 SSH 私钥添加到 `ssh-agent` 并将密码存储在密钥链中。 如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 **id_ed25519** 替换为私钥文件的名称 + + ```bash + ssh-add --apple-use-keychain ~/.ssh/id_ed25519 + ``` + +{% note warning no-icon 注意 %} + +1. 当你将 SSH 密钥添加到 ssh-agent 时,`--apple-use-keychain` 选项会将密码存储在你的密钥链中。 如果选择不向密钥添加密码,请运行命令,而不使用 `--apple-use-keychain` 选项 +2. 选项 `--apple-use-keychain` 位于 Apple 的 ssh-add 标准版本中。 在 Monterey (12.0) 之前的 macOS 版本中,`--apple-use-keychain` 和 `--apple-load-keychain` 标志分别使用语法 {% label @-K %} 和 {% label @-A %} +3. 如果您没有安装 Apple 的 ssh-add 标准版本,可能会收到错误消息。 有关详细信息,请参阅 “[错误:ssh-add:非法选项 -- apple-use-keychain](https://docs.github.com/zh/authentication/troubleshooting-ssh/error-ssh-add-illegal-option----apple-use-keychain)” +4. 如果系统继续提示你输入密码,则可能需要将命令添加到 `~/.zshrc` 文件(或 bash 对应的 `~/.bashrc` 文件) + +{% endnote %} + + + + + +1. 在后台启动 ssh 代理 + + ```bash + $ eval "$(ssh-agent -s)" + > Agent pid 59566 + ``` + + 根据您的环境,您可能需要使用不同的命令。 例如,在启动 `ssh-agent` 之前,你可能需要通过运行 `sudo -s -H` 根访问,或者可能需要使用 `exec ssh-agent bash` 或 `exec ssh-agent zsh` 运行 `ssh-agent` + +2. 将 SSH 私钥添加到 ssh-agent + + 如果使用其他名称创建了密钥,或要添加具有其他名称的现有密钥,请将命令中的 **id_ed25519** 替换为私钥文件的名称 + + ```bash + ssh-add ~/.ssh/id_ed25519 + ``` + + +{% endtabs %} +{% endnote %} + +除了上述的方式生成密钥,还有 [为硬件安全密钥生成新的 SSH 密钥](https://docs.github.com/zh/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key-for-a-hardware-security-key) 方式,这里不做说明,可以移步官方文档查看 + +#### 将 SSH 密钥添加到 GitHub 账户 + +![github-ssh-add](https://res.cloudinary.com/incoder/image/upload/v1722781365/blog/github-ssh-add.png) + +> 其他更多设置可参考官方文档 [新增 SSH 密钥到 GitHub 帐户](https://docs.github.com/zh/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account) + +#### 将签名密钥告诉 Git + +{% note warning %} + +- 如需全局配置:添加 `--global` 参数 +- 如果你有多个 Git 账号,推荐 按照不同的账号分别进行配置,具体可参考 [Git 多账号配置](git-account.md#添加配置文件) 这篇文章 + +{% endnote %} + +```bash +# 1. 配置 Git 使用 SSH 对提交和标记签名 +git config gpg.format ssh +# 2. 配置指定 SSH 签名密钥 +# 将 ~/.ssh/id_ed25519 替换为要使用的公钥路径 +git config user.signingkey ~/.ssh/id_ed25519.pub +# commit 开启自动签名 +git config commit.gpgsign true +# tag 开启自动签名 +git config tag.gpgsign true +``` + +#### 配置可信公钥列表 + +```bash +mkdir -p ~/.config/git +touch ~/.config/git/allowed_signers +# 将指定的密钥文件(~/.ssh/id_ed25519)内容复制到 allowed_signers 文件中 +# 如果是继续追加, 将 > 替换为 >> +cat ~/.ssh/id_ed25519.pub > ~/.config/git/allowed_signers +# 配置可信公钥 +git config gpg.ssh.allowedSignersFile "~/.config/git/allowed_signers" +``` + +#### 对 commit 签名 + +```bash +# 1. 当本地分支中的提交更改时,请将 -S 标志添加到 git commit 命令 +git commit -S -m "YOUR_COMMIT_MESSAGE" +# 2. 在本地完成创建提交后,将其推送到 GitHub 上的远程仓库 +git push +``` + +#### 对 tag 签名 + +> 注意:如果 Git 客户端配置为默认对提交进行签名,GitHub Desktop 仅支持提交签名 + +```bash +# 1. 若要对标记进行签名,请将 -s 添加到 git tag 命令 +git tag -s MYTAG +# 2. 通过运行 git tag -v [tag-name] 验证已签名的标记 +git tag -v MYTAG +``` + +### GPG + +> 具体实践可参考官方文档 [GPG 提交签名验证](https://docs.github.com/zh/authentication/managing-commit-signature-verification/about-commit-signature-verification#gpg-commit-signature-verification) + +### S/MIME + +> S/MIME 签名验证需 Git 2.19 及以上版本 + +由于 S/MIME 用的比较少,这里就不做具体的演示,可参考 [官方文档](https://docs.github.com/zh/authentication/managing-commit-signature-verification/about-commit-signature-verification#smime-commit-signature-verification) + +## 验证签名 + +```bash +# 查看一下本地签名信息 +# 正常情况,在 commit 提交号下 +# good "git" signature for $(email) with $(publicKey) +git log --show-signature +``` + +## 问题 + +### 验证签名提示异常 + +{% tabs signature unique name %} + + + +No signature: + +- 表示 Git 不知道要信任哪些 SSH 密钥 +- 解决方法:配置[可信公钥列表](#配置可信公钥列表) + +![no-signature](https://res.cloudinary.com/incoder/image/upload/v1723359614/blog/git-no-signature.png) + + + + + +No principal matched + +![no-principal](https://res.cloudinary.com/incoder/image/upload/v1723359614/blog/git-no-principal.png) + + + + + +invalid key + +![invalid-key](https://res.cloudinary.com/incoder/image/upload/v1723359614/blog/git-invalid-key.png) + + + +{% endtabs %} + +### 如何给现有密钥更新密码 + +通过输入以下命令,您可以 更改 **现有私钥** 的密码而无需重新生成密钥对 + +```bash +# 修改 id_ed25519 密钥密码 +$ ssh-keygen -p -f ~/.ssh/id_ed25519 +> Enter old passphrase: [Type old passphrase] +> Key has comment 'your_email@example.com' +> Enter new passphrase (empty for no passphrase): [Type new passphrase] +> Enter same passphrase again: [Repeat the new passphrase] +> Your identification has been saved with the new passphrase. +``` + +## 参考 + +1. [Git 提交使用 SSH 签名和 GPG 签名验证](https://piaohua.github.io/post/git/20230624-git-ssh-gpg/) +2. [SSH 提交签名验证](https://lruihao.cn/posts/ssh-sign/) +3. [GitHub commit 签名指南](https://ayk.moe/articles/commit-signature-guide/index.html) +4. [维护代码的尊严:GPG签名让你的Git commit不再裸奔](https://juejin.cn/post/7268593569782300727) diff --git a/source/_posts/git-sub.md b/source/_posts/git-sub.md new file mode 100644 index 000000000..f61b2dd77 --- /dev/null +++ b/source/_posts/git-sub.md @@ -0,0 +1,231 @@ +--- +title: Git 子仓库管理 +date: 2018-05-17 10:30:50 +categories: Git +tag: [git subtree, git submodule] +--- + +在使用 NexT 作为 Hexo 博客的主题时,不能 **友好** 的支持其主题的更新,以及 **多设备** 之间的主题同步。 + +按照官方提供的导入主题操作指引 + +```bash +$ cd hexo +$ git clone https://github.com/theme-next/hexo-theme-next themes/next +``` + +发现commit并push到GitHub的远程服务器上,发现 `themes/next` 路径下并不能打开和查看该路径下的文件,原因是NexT是当前项目的一个子仓库(项目),在 Github 上对于之仓库项目的引用,推荐使用 `git subtree` 命令来进行对子仓库的管理,不推荐直接拷贝需要子仓库的代码到自己的项目中 + + + +原因是我是使用 Travis CI 来部署自己的项目,具体的[构建脚本和介绍](https://incoder.org/2018/05/02/hexo-iterative)请看,下面分别使用 `git submodule`、`git subtree` 的方式进行 NexT 主题的管理 + +## git submodule 与 git subtree + +{% note primary %} +`git submodule`、`git subtree`都可以实现一个仓库作为其他仓库的子仓库的管理 +{% endnote %} + +* `git submodule`:是 Git 官方以前的推荐方案 +* `git subtree`:Git [1.5.2](https://lwn.net/Articles/235109) 开始,Git 新增并推荐使用这个功能来管理子项目 +* `git subtree`与`git submodule`不同,它不增加任何像 `.gitmodule` 这样的新的元数据文件 +* `git subtree`对于项目中的其他成员透明,意味着可以不知道 `git subtree` 的存在 + +## git submodule 常用操作 + +[Git Submodule](https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E5%AD%90%E6%A8%A1%E5%9D%97)功能官方操作指引 + +### add 一个submodule + +1. Fork Repository + [hexo-theme-next](https://github.com/theme-next/hexo-theme-next)项目右上角 `Fork` 按钮即可 +2. Clone Repository + ```bash + git clone git@github.com:RootCluster/hexo-theme-test.git + ``` +3. Add Submodule + ```bash + # 进入项目 + cd hexo-theme-test + # 注册 next 项目是一个submodule,并把数据拷贝到 `themes/next` 路径 + git submodule add git@github.com:RootCluster/hexo-theme-next.git themes/next + ``` +4. status + ```bash + # 当前 submodule 已被注册并指向了某个 commit + git submodule status + 1f5643061ec5257269673bd6159403c24015c53d themes/next (v6.3.0) + # 查看在父仓库中有哪些变化被注册 + git status + On branch submodule + Changes to be committed: + (use "git reset HEAD ..." to unstage) + new file: .gitmodules + new file: themes/next + ``` + + >有2个文件被修改过:`.gitmodules`,`themes/next`,当在父仓库时,Git 不会跟踪 submodule 中的文件,Git 只把它当成一个单一的文件 + + * `.gitmodules`:存有 submodule 的信息 + * `themes/next`:submodule 它自己 + +5. commint + ```bash + # 推送到远程 submodule 分支 + git commit -am "add next submodule" + [submodule a5a612b] add next submodule + 2 files changed, 4 insertions(+) + create mode 100644 .gitmodules + create mode 160000 themes/next + ``` +6. push + ```bash + git push origin submodule + Counting objects: 4, done. + Delta compression using up to 4 threads. + Compressing objects: 100% (4/4), done. + Writing objects: 100% (4/4), 451 bytes | 451.00 KiB/s, done. + Total 4 (delta 1), reused 0 (delta 0) + remote: Resolving deltas: 100% (1/1), completed with 1 local object. + To github.com:RootCluster/hexo-themes-test.git + 71879a8..a5a612b submodule -> submodule + ``` +查看 Github 上的仓库,发现父仓库里有一个指向 submodule 的链接,表示你已经成功添加了一个 submodule + +### clone 带 submodule 的项目 + +新路径下,clone 项目,submodule 分支 + +```bash +# clone 项目 +git clone -b submodule git@github.com:RootCluster/hexo-themes-test.git +# 进入项目路径 +cd hexo-themes-test/ +# 项目注册 submodule +git submodule init +# clone submodule 代码 +git submodule update + +``` + +### update 带 submodule 的项目 + +只要在 submodule 路径下,所有的常规Git操作,如 `push`, `pull`, `reset`, `status` 等,都可以正常工作,如果要保证 submodule 和远程仓库保存同步,在 submodule 路径下运行 `git pull` + +* 如果你得到一个错误信息, 说你不在任何分支之上, 只要运行 `git checkout master` 就可修复 +* 如果你在 `pull` 后 `submodule` 有一些更新, 父仓库会告诉你有一些变动需要 `commit` 了, `submodule`自身指向一个指定的 `commit`, 并且如果这个 `commit` 改变了, 父仓库会得知这个改变. 如果你的 `submodule` 需要在一个指定 `commit` 上工作, 可用 `git reset` 来设置 + +例如:我需要把NexT的版本改变到上一个 Tag 6.2.0 (目前是6.3.0) +> git reset --hard (commit hash) + +```bash +# 进入项目路径 +cd hexo-themes-test/ +# 重新指向 submodule 关联的 commit 记录 +git reset --hard 206d463 +# 回到父目录 +cd .. +# commit 本次的修改 +git commit -am "set next version to 6.2.0" +``` + +{% note info %} 推送到远程仓库后,`submodule` 会和指定的`commit` 关联起来。如果你和别人一起工作在同一个项目,别人也可以在`submodule`下`pull`并且`commit`,因此改变了`submodule`的`commit`指向,这个问题,可以通过`git reset` 来解决{% endnote %} + +### remove 项目中的 submodule + +* 项目的根目录下(不是 submodule 的目录),编辑 .gitmodules 文件,删除submodule配置 + ```bash + [submodule "themes/next"] + path = themes/next + url = https://github.com/RootCluster/hexo-theme-next.git + ``` +* 项目根目录下,编辑 `.git` 文件夹下 `config` 文件,删除 submodule 配置 + ```bash + [submodule "themes/next"] + url = https://github.com/RootCluster/hexo-theme-next.git + ``` +* 清除 submodule 缓存 + ```bash + git rm --cached themes/next + ``` + +## git subtree 常用操作(重点) + +### add一个subtree + +* 在父仓库中新增子仓库 + ```bash + # 添加子仓库 + git subtree add --prefix=themes/next https://github.com/RootCluster/hexo-theme-next.git master --squash + git fetch https://github.com/RootCluster/hexo-theme-next.git master + warning: no common commits + remote: Counting objects: 3407, done. + remote: Total 3407 (delta 0), reused 0 (delta 0), pack-reused 3406 + Receiving objects: 100% (3407/3407), 1.21 MiB | 36.00 KiB/s, done. + Resolving deltas: 100% (2192/2192), done. + From https://github.com/RootCluster/hexo-theme-next + * branch master -> FETCH_HEAD + Added dir 'themes/next' + ``` + >`--squash`参数表示不拉取历史信息,而只生成一条 commit 信息 + +* 查看项目状态 + ```bash + # 查看项目状态 + git status + On branch subtree + Your branch is ahead of 'origin/subtree' by 2 commits. + (use "git push" to publish your local commits) + + nothing to commit, working tree clean + ``` + +* 推送更改到远程仓库 + ```bash + git push origin subtree + Counting objects: 381, done. + Delta compression using up to 4 threads. + Compressing objects: 100% (334/334), done. + Writing objects: 100% (381/381), 650.26 KiB | 34.22 MiB/s, done. + Total 381 (delta 23), reused 225 (delta 19) + remote: Resolving deltas: 100% (23/23), completed with 1 local object. + To https://github.com/RootCluster/hexo-themes-test.git + 8ed2e2e..405af42 subtree -> subtree + ``` + +### pull 子仓库更新 + +```bash +# 更新子仓库 +git subtree pull --prefix=themes/next https://github.com/RootCluster/hexo-theme-next.git master --squash +From https://github.com/RootCluster/hexo-theme-next + * branch master -> FETCH_HEAD +Subtree is already at commit 1f5643061ec5257269673bd6159403c24015c53d. +``` + +### push 子仓库修改 + +在引用子仓库的项目中修改了子仓库的相关代码,推送修改到源仓库 + +* commit 修改记录 +* push 到源仓库 + ```bash + # 推送子仓库修改到源仓库master分支 + git subtree push --prefix=themes/next https://github.com/RootCluster/hexo-theme-next.git master + ``` + +### subtree 常用命令 + +```bash +git subtree add --prefix= +git subtree add --prefix= +git subtree pull --prefix= +git subtree push --prefix= +git subtree merge --prefix= +git subtree split --prefix= [OPTIONS] [] +``` + +## 附录 + +* [如何使用 Git Submodule](http://linlexus.com/git-submodule-usage) +* [git subtree教程](https://www.jianshu.com/p/d42d330bfead) \ No newline at end of file diff --git a/source/_posts/gitignore.md b/source/_posts/gitignore.md new file mode 100644 index 000000000..61c361f43 --- /dev/null +++ b/source/_posts/gitignore.md @@ -0,0 +1,46 @@ +--- +title: .gitignore 基础知识 +date: 2018-04-13 00:30:50 +categories: Git +tag: ignore +--- + +.gitignore顾名思义是Git中用来管理所需要忽略或者说不用纳入版本控制文件 + +## 基本配置语法 +1. “#“:表示注释 +2. “/“:表示目录 +3. “*“:表示通配符,用来通配多个字符 +4. “?“:表示通配单个字符 +5. “[]“:表示包含单个字符的匹配列表 +6. “!“:表示不忽略匹配到的文件或者目录 + + + +>注意:Git对.gitignore配置文件是从上往下进行规则匹配,这也意味如果:前(limit)>后(limit),则后面的规则不会被执行 + +## 全局与局部 +.gitignore分为: **全局** ignore,**局部** ignore + +### 全局ignore设置 +* 在用户账户文件夹(C:\Users\<'YourName'>)路径下新建一个命名为`.gitignore_global`的文件 +* 使用Git Bash(需要切换路径到C:\Users\<'YourName'>)或者Git CMD命令行工具输入: + ``` bash + git config --global core.excludesfile ~/.gitignore_global + ``` +* 此时全局ignore已经设置完成,你只需要修改`.gitignore_global`文件内需要忽略的文件类型就可以全局控制忽略不需要纳入版本控制的文件或文件夹 +* 不难发现,其实是往 `.gitconfig`中加入如下内容来指名Git忽略不纳入版本控制的文件,当然如果你不想用命令行完成全局设置,你也可以直接在`.gitconfig`文件中加入`[core] excludesfile= ~/.gitignore_global`内容即可 + +### 局部ignore设置 +* 只需要在Git控制版本控制项目的根目录中加入.gitignore文件,在.gitignore文件中写明忽略不纳入版本控制的文件即可 + +## 参考示例 + +>你可以查看参考[Github](https://github.com/github/gitignore)官方所写好的示例 + +## 插件.ignore +支持Android Studio,JetBrains系列 +安装方法 + +* `Settings` > `Plugs` > `Browse repositories` > `.ignore` > `Install plugin` +* 里面有已经写好的模板,只需适当修改 \ No newline at end of file diff --git a/source/_posts/gitlab1.md b/source/_posts/gitlab1.md new file mode 100644 index 000000000..5baf70e3f --- /dev/null +++ b/source/_posts/gitlab1.md @@ -0,0 +1,143 @@ +--- +title: Gitlab 应用搭建 +date: 2018-04-24 21:11:10 +categories: Git +tag: Gitlab +--- + +我司团队之前一直使用SVN来进行代码托管,主要问题 +1. 每次来个新人都需要找对应的[SVN](https://tortoisesvn.net/index.zh.html)管理员进行授权分配指定的仓库操作权限,有时候需要多个项目切换,还得再次提出进行仓库的指定 +2. SVN都是以中文命名,这其实没啥,但是在[eclipse](https://eclipse.org) 以及[IDEA](https://www.jetbrains.com/idea/?fromMenu) ,[Xcode](https://developer.apple.com/xcode)等开发工具,链接地址都会把中文字进行编码,造成路径非常的长,强迫症的我这怎么忍得了 +3. 产品相关的,设计相关的啥也都放在SVN里面,搞得SVN里面鱼龙混杂 + + + +因此在我提出及建议下,部门经理同意了对代码的管理进行隔离方便有效的对代码的授权监管,并同时制定代码的相关规范和服务的自动化部署等,提高团队的开发效率和代码质量。 + +本节主要介绍Gitlab的环境搭建和基础的功能配置 + +目的: +1. 搭建Gitlab服务 +2. 和公司AD域账号关联,用域账号直接登录Gitlab +3. 挂载Gitlab 仓库到指定存储位置 + +## Gitlab安装 + +### 环境 +* OS:CentOS 7 +* Gitlab:[Gitlab CE 10.6.4](https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-10.6.4-ce.0.el7.x86_64.rpm) + +>Gitlab 版本 +* Gitlab Community Edition (CE):社区版,免费,用户自行托管,通过社区提供技术支持 +* Gitlab Enterprise Edition (EE):企业版,付费,用户自行托管,提供附加的功能以及技术支持 +* Gitlab.com:免费的SaaS服务,可以创建共有以及私有的版本库,可以购买额外的技术支持 +* GitHost.io:由Gitlab提供的用户私有的独享服务 + +### Gitlab部署 +1. 系统防火墙中打开HTTP和SSH访问 + ```bash + sudo yum install -y curl policycoreutils-python openssh-server + sudo systemctl enable sshd + sudo systemctl start sshd + + sudo firewall-cmd --permanent --add-service=http + sudo systemctl reload firewalld + ``` +2. 安装Postfix发送通知邮件。如果您想使用其他解决方案发送电子邮件,请跳过此步骤并在安装GitLab后配置外部SMTP服务器 + ```bash + sudo yum install postfix + sudo systemctl enable postfix + sudo systemctl start postfix + ``` +3. 添加GitLab软件包存储库 + ```bash + curl -LJO https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-10.0.0-ce.0.el7.x86_64.rpm + ``` +4. 安装软件包 + ```bash + rpm -i gitlab-ce-10.0.0-ce.0.el7.x86_64.rpm + ``` + + 完成安装如下日志显示: + + ``` + *. *. + *** *** + ***** ***** + .****** ******* + ******** ******** + ,,,,,,,,,***********,,,,,,,,, + ,,,,,,,,,,,*********,,,,,,,,,,, + .,,,,,,,,,,,*******,,,,,,,,,,,, + ,,,,,,,,,*****,,,,,,,,,. + ,,,,,,,****,,,,,, + .,,,***,,,, + ,*,. + + + + _______ __ __ __ + / ____(_) /_/ / ____ _/ /_ + / / __/ / __/ / / __ \`/ __ \ + / /_/ / / /_/ /___/ /_/ / /_/ / + \____/_/\__/_____/\__,_/_.___/ + + ``` +5. 编译配置文件 + ```bash + cd /opt/gitlab/bin + ./gitlab-ctr reconfigure + ``` +6. 启动服务 + ```bash + ./gitlab-ctl start + ``` + > + * 成功启动服务,默认路径访问:http://localhost:80 + * 默认安装位置 `/opt/gitlab/` + * 配置文件默认路径 `/etc/gitlab/gitlab.rb` + * 默认账号:root,密码:5iveL!fe + +## 常用配置项修改 +以下配置项的修改,完成后**均需要重新编译**文件(配置文件默认路径 `/etc/gitlab/gitlab.rb`),默认,**并重启Gitlab**服务 + +### 访问地址 +修改`external_url`为Gitlab对应机器IP所配置的域名 +![gitlab-url](https://res.cloudinary.com/incoder/image/upload/v1525517587/blog/gitpages-gitlab-url.png) + +### LDAP启用 +修改`host`,`port`,`bind_dn`,`password`,`base`参数即可 +![gitlab-ladp](https://res.cloudinary.com/incoder/image/upload/v1525517612/blog/gitpages-gitlab-ldap.png) + +各参数解释: +* `host` 和 `port` 是 LDAP 服务的主机地址及端口 +* `bind_d`n 和 `password` 是一个管理 LDAP 的 dn 及密码 +* `base` 表示 LDAP 将以该 dn 为 节点,向下查找用户 +* `user_filter` 表示以某种过滤条件筛选用户 +* `attributes` 表示 GitLab 中的字段与 LDAP 中哪些字段可以相互对应,比如可以用 LDAP 中的 uid 来作为 GitLab 用户名 + +编译重启后,查看登录是否已经显示LDAP登录入口 + +![gitlab-ldap-login](https://res.cloudinary.com/incoder/image/upload/v1525517639/blog/gitpages-gitlab-ldap-login.png) + +为了安全我们需要关闭 GitLab 自己的注册功能,这样新用户只能通过 LDAP 认证的方式进行登陆。 + +![gitlab-sign-up](https://res.cloudinary.com/incoder/image/upload/v1525517671/blog/gitpages-gitlab-sign-up.png) + +### 存储仓库修改 +默认仓库存储位置:`/var/opt/gitlab/git-data/repositories/` +![gitlab-dirs](https://res.cloudinary.com/incoder/image/upload/v1525517697/blog/gitpages-gitlab-dirs.png) + +### Gitlab日志 +默认日志位置: `/var/log/gitlab` + +```bash +cd /opt/gitlab/bin +gitlab-ctl tail -f nginx/gitlab_access.log +``` +或者在Gitlab服务的系统设置中查看 +![gitlab-logs](https://res.cloudinary.com/incoder/image/upload/v1525517725/blog/gitpages-gitlab-logs.png) + +## 附录 +* [官方安装教程](https://about.gitlab.com/installation) +* [官方配置文件](https://docs.gitlab.com.cn/omnibus/settings/README.html) \ No newline at end of file diff --git a/source/_posts/gradle1.md b/source/_posts/gradle1.md new file mode 100644 index 000000000..749db4d28 --- /dev/null +++ b/source/_posts/gradle1.md @@ -0,0 +1,230 @@ +--- +title: Gradle(一)基础 +date: 2020-12-10 10:11:46 +categories: Gradle +tag: [Gradle] +--- + +GitHub 上 Gralde 是这样描述,"Adaptable, fast automation for all"(让一切都能`快速`的`自动化`) +Gradle是一个构建工具,专注于构建自动化和对多语言开发的支持。对于在任何平台上的构建,测试,发布和部署,Gralde 提供了一种灵活的模型,可以支持从编译和打包代码到发布网站的整个生命周期。Gralde 旨在支持跨多种语言和平台的构建自动化,包括 Java,Scala,Android,Kotlin,C/C++ 和 Groovy,并于开发工具和包括 Eclipse,IntelliJ 和 Jenkins 的持续集成服务器紧密集成 + + + +* Gradle official:https://gradle.org +* Gradle docs:https://docs.gradle.org +* Gradle plugins:https://plugins.gradle.org + +## Gradle 特点 + +1. Gradle 基于 JVM 的构建工具 +2. 兼容支持 Maven,Ant 等 +3. 支持基于 Groovy 的构建脚本 +4. 编译构建执行效率更高 +5. 支持多种语言等 +6. 易于迁移 + +## Gradle 安装配置 + +* Gradle 官方:https://services.gradle.org/distributions/ +* Tencent 镜像:https://mirrors.cloud.tencent.com/gradle/ + +> Tencent Gradle 镜像同步有一定的延迟,需要注意 + +### 下载 + +下载需要的版本即可,推荐最新版,这里以最新稳定版 6.7.1 为例,每个正式版本包含如下文件,我们选择 `xxx-bin.zip`(二进制版,只包含了二进制文件(可执行文件),没有文档和源代码) 或 `xxx-all.zip`(完整版,包含了各种二进制文件,源代码文件,和离线的文档)的文件即可,进行手动安装 + +```sh +gradle-6.7.1 + ├── gradle-6.7.1-wrapper.jar.sha256 # wrapper.jar hash 校验文件 + ├── gradle-6.7.1-docs.zip # gradle 文档压缩文件 + ├── gradle-6.7.1-docs.zip.sha256 # gradle 文档 hash 校验文件 + ├── gradle-6.7.1-src.zip # gradle 源码版,只包含了 Gradle 源代码,不能用来编译你的工程 + ├── gradle-6.7.1-src.zip.sha256 # gradle 源码版 hash 校验文件 + ├── gradle-6.7.1-bin.zip # gradle 核心压缩文件 + ├── gradle-6.7.1-bin.zip.sha256 # gradle 核心 hash 校验文件 + ├── gradle-6.7.1-all.zip # gradle 全部资源压缩文件 + └── gradle-6.7.1-all.zip.sha256 # gradle 全部资源 hash 校验文件 +``` +当然如果你使用的 macOS 系统,且也已经安装了 `homebrew` 包管理工具,那么同样你也可以使用 brew 命令来安装 Gradle,那么你将不需要再去手动配置 Gradle 的环境,它的安装默认路径在 `/usr/local/bin/gradle`,安装完成后你就可以使用 gradle 的相关命令 +```bash +# gradle 安装 +brew install gradle +# gradle 升级 +brew upgrade gradle +# 检查是否安装成功 +gradle -v +``` + +### 配置 + +手动下载解压的文件进行安装,则需要配置 Gradle 的环境,这样方便我们在任何地方都可以调用 Gradle 的命令,对于 macOS 上手动安装配置 Gradle 环境的操作,可以参考 [MacBook Pro 初始化](https://incoder.org/2018/11/10/mac-init/#Gradle配置) 这篇文章 Gradle 配置 + +对于 Windows 系统,按照如下步骤进行添加环境变量,我这里 Windows 上为了和项目中 Gradle 版本有所区分,配置的是 6.7 版本 +![](https://res.cloudinary.com/incoder/image/upload/v1608634993/blog/gradle-home.png) +![](https://res.cloudinary.com/incoder/image/upload/v1608634993/blog/gradle-path.png) +配置完成后,老规矩我们需要验证下我们的配置是否生效,在命令行中输入 `gradle -v` 命令,查看有 Gradle 相关的版本信息提示,我们的配置就已成功 +![](https://res.cloudinary.com/incoder/image/upload/v1608634993/blog/gradlew-or-gradle.png) + +#### GRADLE_HOME + +GRADLE_HOME 这个环境变量,它主要是我们手动配置指定 GRADLE 使用的命令环境 + +#### GRADLE_USER_HOME + +GRADLE_USER_HOME 指配置 Gradle 的安装下载的路径。默认 `/Users//.gradle` 路径,如果你在系统环境中设置了 GRADLE_USER_HOME 的环境变量,那么下载的路径就变成了你自定义设置的路径 + +## Gradle 基础 + +刚刚在上面我们配置时,使用了 `gradlew` 命令,那这个又是啥呢,这里简单解释下,gradlew 是 gradle wrapper 的简写,对于 Gradle 构建的项目,用于解决 Gradle 安装,部署以及统一项目的 Gradle 的构建版本等一系列问题。 + +Gradle 有两个基本的概念:project 和 task,Gradle 里面的所有东西基于这两个概念 +* project:通常指一个项目 +* task:指构建过程中的任务 + +一次构建可以有 1 到 n 个 project,而每个 project 有 1 到 n 个 task + +## Gradle 项目 + +Android 项目工程一开始就默认使用 Gradle 来构建,在 Android 领域里使用花样也是比较多,更好体现了 Gradle 的灵活性,对于后端 Spring 系列项目,现在也是越来越多的开始使用 Gradle 来构建了,在 [Spring Boot 2.3.0.M1](https://spring.io/blog/2020/06/08/migrating-spring-boot-s-build-to-gradle) 版本官方已开始在生产环境开始使用 Gradle 代替 Maven 进行构建,测试,发布项目。这从侧面也印证了 Gradle 对于复杂庞大的系统更加友好和**高效**。 + +对于使用 Gradle 构建的 Android 项目也好,Java 项目也好,还是 SpringBoot 项目也罢,它们都有共同的特点。在结构上有下面的相同点 + +```bash +project + ├── …… + ├── .gradle/ # 项目使用 gradle 编译生成的临时文件存放位置 + ├── gradle/wrapper + │ │── gradle-wrapper.jar # gradlew 核心执行文件 + │ └── gradle-wrapper.properties # gradle 运行环境配置文件 + ├── build.gradle # 项目依赖配置,脚本配置文件 + ├── gradlew # Linux or macOS 下可执行脚本 + ├── gradlew.bat # Windows 下可执行脚本 + ├── settings.gradle # 配置构建应用时应将哪些模块包含在内 + └── …… +``` + +### gradle-wrapper.properties + +* gradle-wrapper.jar 文件是项目中执行 gradlew 相关命令的具体实现,感兴趣的可以查看其中的具体源码实现 +* gradle-wrapper.properties 是 Gradle 项目版本管理的核心 + * distributionBase=GRADLE_USER_HOME:指定了 wrapper 保存下载的 Gradle 的`主路径` + * distributionPath=wrapper/dists:指定了 wrapper 保存下载的 Gradle 的`子路径` + * zipStoreBase=GRADLE_USER_HOME:指定了 wrapper 保存下载 gradle-6.7.1-bin.zip 文件的`主路径` + * zipStorePath=wrapper/dists:指定了 gradle-6.7.1-bin.zip 文件的`子路径` + * distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip:gradle 文件下载的源地址 + +{% note info %} +distributionBase 和 zipStoreBase 有两种取值 +* GRADLE_USER_HOME:默认使用方式,表示用户目录,默认路径 `/Users//.gradle` +* PROJECT:表示工程的当前目录,不常用 +{% endnote %} + +对应 Gradle 的下载及解压目录这里还需要注意下 +{% note danger %} +Gradle 的存放地址,比如:~/.gradle/wrapper/dists/gradle-6.7.1-bin/`bwlcbys1h7rz3272sye1xwiv6` 这里一个看起来无规则的文件夹,我们的 gradle 下载及解压必须放在这个文件夹内,而这个看似无规则的文件夹,实质是根据 distributionUrl 路径字符串计算 md5 值得来的 +{% endnote %} + +### build.gradle 及 settings.gradle + +对于`build.gradle` 及 `settings.gradle` 文件在 Android 应用和 SpringBoot 应用是不一样,因此关于他两介绍请移步 [Gradle(二)Android](https://incoder.org/2020/12/15/gradle2),[Gradle(三)SpringBoot]() 文章进行查看 + +## Gradle 依赖 + +用于声明依赖关系的配置 + +| 配置名称 | 角色 | 是否可消费 | 是否可分解 | 描述 | +|:------------------:|:-----------:|:-----:|:-----:|:---------------------------------------------------------:| +| `api` | 声明API依赖项 | N | N | 在这里,您可以声明依赖关系,这些依赖关系会在编译时和运行时以可传递方式导出到使用者 | +| `implementation` | 声明实现依赖性 | N | N | 在这里,您可以声明纯属内部的依赖关系,而不是要向使用方公开(在运行时仍向使用方公开) | +| `compileOnly` | 声明仅编译依赖项 | N | N | 在这里可以声明在编译时需要的依赖项,而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项 | +| compileOnlyApi | 声明仅编译API依赖项 | N | N | 在这里,您可以声明模块和使用者在编译时需要的依赖项,而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项 | +| `runtimeOnly` | 声明运行时依赖项 | N | N | 在这里可以声明仅在运行时才需要的依赖关系,而在编译时则不需要 | +| testImplementation | 测试依赖 | N | N | 在这里声明用于编译测试的依赖项 | +| testCompileOnly | 声明测试仅编译依赖项 | N | N | 在这里声明仅在测试编译时需要的依赖项,而不应泄漏到运行时。这通常包括在运行时找到时会被阴影化的依赖项 | +| testRuntimeOnly | 声明测试运行时依赖项 | N | N | 在这里可以声明仅在测试运行时才需要的依赖项,而在测试编译时则不需要 | + +核心需要掌握的是 `api`,`implementation`,`compileOnly`,`runtimeOnly` 这4种依赖方式 + +>对于你可能看到依赖方式,compile(api),provided(compileOnly),apk(runtimeOnly) 这些方式是比较旧的依赖方式,在 gradle plugin 3.0 开始已废弃,请使用新的依赖方式 + +### 本地依赖 + +#### 本地依赖 module lib + +通过这种方式依赖的弊端是每次都需要构建 module,但 module 比较多时构建非常耗时,建议控制 module 的依赖数量,避免构建耗时 + +```groovy +// module 需要在项目根目录下的 settings.gradle 中通过 include 引入 +implementation project(':libname') +``` + +#### 本地二进制 lib 依赖 + +本地的 jar 或者 aar 需要放在 module 的 libs 文件夹下,通过这种方式依赖 + +##### 依赖 jar + +```groovy +// 方式一:可以一次性依赖 libs 下的所有 jar +implementation fileTree(dir: 'libs', include: ['*.jar']) + +// 方式二:可以指定依赖一个或几个 jar +implementation files('libs/xxxx1.jar', 'libs/xxxx2.jar') +``` + +##### 依赖 aar + +```groovy +// 在 module 的 build.gradle 中添加目录指定 +repositories { + flatDir { + dirs 'libs' + } +} + +// 在 dependencies 中加入对 aar 的引入 +// 方式一:可以一次性依赖 libs 下所有的 aar +implementation fileTree(dir: 'libs', include: ['*.aar']) +// 方式二:可以指定依赖某一个aar +implementation files(name: 'aar-lib-name', ext: 'aar') +``` + +### 远程二进制 lib 依赖 + +```groovy +// 依赖明确的版本,标明 group、name 和 version +implementation group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.1.1' + +// 常用的简写方式引用 +implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1' +``` + +## Gradle 命令 + +在项目中推荐使用 gradlew 命令来进行执行,这样本质是使用项目所依赖的 Gradle 版本进行执行。当然如果你本地配置了 Gradle 的环境变量,你可以将 `gradlew` 命令更改成 `gradle` 来执行 + +* gradlew clean: 清除 build 文件夹 +* gradlew check: 执行 lint 检查 +* gradlew assemble: 编译并打包你的代码,但并不运行单元测试 +* gradlew build: 编译和测试你的代码,并生成一个包含所有类与资源的文件 +* gradlew dependencies: 查看所有依赖库 + * gradlew dependencies -configuration runtime: 查看运行时依赖库 + +{% note warning %} +注意: +* Windows:在项目根目录,使用的是 `gradlew` +* Linux or macOS:在项目的根目录,使用的是 `./gradlew` +{% endnote %} + +## 总结 + +1. 对于 Gradle 我们不需要配置 GRADLE_USER_HOME 的环境,原因是项目中已对使用 Gradle 的版本做出了统一,我们仅需要根据自身的网络需要(如果从默认地址下载很慢,则需要配置好项目依赖镜像源)做出合适的配置。而如果你需要在任何地方使用 `gradle` 相关的命令,则配置 GRADLE_HOME 即可 +2. 依赖方式,我们选择 implementation 方式,这样可屏蔽掉不同应用之间因为引用了同一 lib 而不同版本造成的麻烦问题等 + +## 参考 + +* [gradle-wrapper.properties中各属性的含义](https://blog.csdn.net/u013553529/article/details/55011602) +* [Dependency management in Gradle](https://docs.gradle.org/current/userguide/core_dependency_management.html) +* [The Java Library Plugin](https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph) +* [The Distribution Plugin](https://docs.gradle.org/current/userguide/distribution_plugin.html) \ No newline at end of file diff --git a/source/_posts/gradle2.md b/source/_posts/gradle2.md new file mode 100644 index 000000000..20606c57e --- /dev/null +++ b/source/_posts/gradle2.md @@ -0,0 +1,466 @@ +--- +title: Gradle(二)Android +date: 2020-12-15 09:00:46 +categories: Gradle +tag: [Gradle] +--- + +在上一篇 Gradle 的文章中,已经对 Gradle 有了一定的认识,Gradle 在 Android 有着广泛的应用,用作 Android 包依赖管理,应用构建,测试,等一些列自动化,我们本篇就来了解下在 Android 领域 Gradle 的使用。其实 Android 项目结构和之前在第一篇 Gradle 项目结构基本相同,只是在 module 级别多了的 proguard-rules.pro。对于不管是 Android 项目或是 Spring 系列项目的子 module 都会有 build.gradle 文件 + + + +## Project 级别 + +### build.gradle + +```groovy +// gradle 脚本执行所需依赖,分别是对应的maven库和插件 +buildscript { + repositories { + google() + jcenter() + } + // 声明依赖 Android Gradle 插件版本 + dependencies { + classpath 'com.android.tools.build:gradle:3.5.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +// 项目所有 module 配置需要的依赖 +allprojects { + repositories { + google() + jcenter() + } +} + +// 一个 clean 任务,用于删除 build 目录的文件 +task clean(type: Delete) { + delete rootProject.buildDir +} +``` + +### settings.gradle + +```groovy +// 默认指的是创建 Android 项目生成的 app 模块,也是默认的应用启动模块 +include ':app' +``` + +## Module 级别 + +```groovy +// 表示这是一个应用程序模块,可直接运行 +apply plugin: 'com.android.application' + +// 编译时间 +static def releaseTime() { + return new Date().format('yyyy-MM-dd', TimeZone.getTimeZone('UTC')) +} + +android { + // 编译 Android 版本 + compileSdkVersion 29 + // 默认配置 + defaultConfig { + // 应用 ID,手机中用于识别应用的唯一标识 + applicationId "org.incoder.android" + // 目标 Android 版本 + targetSdkVersion 29 + // 申明应用可超过 65536 的方法,可参考:https://developer.android.google.cn/studio/build/multidex?hl=zh_cn + multiDexEnabled true + // 申明要使用AndroidJUnitRunner进行单元测试 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + // 签名配置,相关信息放置在 gradle.properties 文件中 + signingConfigs { + debug { + storeFile file(DEBUG_STORE_FILE) + storePassword DEBUG_STORE_PASSWORD + keyAlias DEBUG_KEY_ALIAS + keyPassword DEBUG_KEY_PASSWORD + } + release { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + v2SigningEnabled true + } + } + + buildTypes { + debug { + minifyEnabled false + zipAlignEnabled false + shrinkResources false + // 签名 +// signingConfig signingConfigs.debug + manifestPlaceholders = [ + //JPush + JPUSH_APPKEY : "", + JPUSH_CHANNEL: "", + // Pgy + PGYER_APPID : "7907554687e4c116316feedb3820ce52", + // Bugly + BUGLY_APPID : "", + VERSION_NAME : "0.1.0", + ] + ndk { + // 设置支持的SO库架构 + abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a' + } + } + + release { + // 混淆 + minifyEnabled false + // Zipalign优化 + zipAlignEnabled true + // 移除无用的resource文件 + shrinkResources false + // 前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明 + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + // 签名 + signingConfig signingConfigs.release + // AppAnalytics key + manifestPlaceholders = [ + // JPush + JPUSH_APPKEY : "", + JPUSH_CHANNEL: "", + // Pgy + PGYER_APPID : "7907554687e4c116316feedb3820ce52", + // Bugly + BUGLY_APPID : "", + VERSION_NAME : "0.1.0", + ] + } + } + + // 重命名安装包 + android.applicationVariants.all { + variant -> + variant.outputs.all { + output -> + output.outputFileName = variant.flavorName + buildType.name + + "_" + releaseTime() + ".apk" + } + } + + // 产品变种 + flavorDimensions "minSDK" + + // 针对不同渠道的配置 + productFlavors { + // 测试环境渠道包 + dev { + applicationId 'org.incoder.test' + minSdkVersion 19 + // 测试环境IP配置 API 接口地址 + buildConfigField 'String', 'API', '"http://xxx.xxx.xxx.xxx:8888"' + versionCode 2020122501 + versionName "2.0" + // 指定产品变种 + dimension "minSDK" + } + // 正式环境渠道包 + rel { + applicationId "org.incoder.android" + minSdkVersion 16 + // 正式环境域名 API 接口地址 + buildConfigField 'String', 'API', '"http://api.xxx.xxx/"' + versionCode 2020122501 + versionName "2.1" + // 指定产品变种 + dimension "minSDK" + } + } + + // 过滤指定产品变种(渠道,构建类型) +// variantFilter { variant -> +// def names = variant.flavors*.name +// def isDebug = variant.buildType.debuggable +// // To check for a certain build type, use variant.buildType.name == "" +// if (names.contains("rel") && isDebug) { +// // Gradle ignores any variants that satisfy the conditions above. +// setIgnore(true) +// } +// } + + // 多渠道配置 + productFlavors.all { + flavor -> + flavor.manifestPlaceholders = [CHANNEL_VALUE: name] + } + + // 执行lint检查,有任何的错误或者警告提示,都会终止构建 + lintOptions { + disable 'MissingTranslation', 'ExtraTranslation' + // abortOnError一定要设为false,这样即使有报错也不会停止打包了 + abortOnError false + // 在打包Release版本的时候进行检测,可以打开,这样报错还会显示出来 + checkReleaseBuilds false + } + + dexOptions { + jumboMode true + javaMaxHeapSize "4g" + } + + // 打包时的配置 + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + exclude 'META-INF/rxjava.properties' + } + + aaptOptions.cruncherEnabled = false + aaptOptions.useNewCruncher = false + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +// 项目依赖的包 +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:design:28.0.0' + implementation 'com.android.support:support-v4:28.0.0' + implementation 'com.android.support:support-v13:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:support-vector-drawable:28.0.0' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + testImplementation 'junit:junit:4.12' +} +``` + +关于 module 中的 build.gradle 配置文件中的各项已在示例中加入了注释说明,其中一些配置,这里再简单的说明下 + +### apply plugin + +这里的 `apply plugin` 有两种模式: +1. com.android.application:表示这是一个应用程序模块 +2. com.android.library:表示这是一个库模块 + +前者可以直接运行,后着是依附别的应用程序运行 + +### buildTypes + +这里主要是生成安装文件的配置信息,一个 debug 类型,用于指定生成测试版安装文件配置,可忽略不写;另一个是 release,用于指定生成正式版安装文件的配置。 + +* minifyEnabled:是否对代码进行混淆,默认 false +* proguardFiles:指定混淆的规则文件,默认指定了 proguard-android.txt 文件和 proguard-rules.pro 文件。 + * proguard-android.txt:默认的混淆文件,里面定义了一些通用的混淆规则 + * proguard-rules.pro:位于当前项目的根目录下,可以在该文件中定义一些项目特有的混淆规则 +* buildConfigField:可用于解决不同渠道不同的服务地址,或不同渠道 LOG 打印控制等 +* debuggable:是否支持断点调试,release 默认为 false,debug 默认 true +* jniDebuggable:是否可以调试 NDK 代码,使用 lldb 进行 C 和 C++ 代码调试,release 默认为 false +* signingConfig:设置签名信息,通过 singingConfig.release 或 singingConfig.debug,配置相应的签名,但是添加此配置前需要先添加 singingConfig 闭包 +* renderscriptDebuggable:是否开启渲染脚本,就是一些 C 写的渲染方法,默认为 false +* renderscriptOptimLevel:渲染等级,默认为 3 +* zipAlignEnabled:是否对 apk 包执行 zip 对齐优化,减少 zip 体积,提高运行效率,release 和 debug 都默认 true +* pseudoLocalesEnabled:是否在 apk 中生成伪语言环境,帮助国际化,一般很少使用 +* applicationIdSuffix:和 defaultConfig 中配置一样,指在 applicationId 中添加一个后缀 +* versionNameSuffix:添加版本名称的后缀,一般使用较少 + +### productFlavors + +这个配置主要是解决应用发布在不同应用市场,而需要对不同应用市场做一些不同配置,比如包名,应用名,以及一些统计,而需要不同渠道统计 ID 等 + +### packagingOptions + +packagingOptions 常见的设置项有 exclude、pickFirst、doNotStrip、merge +1. exclude:过滤掉某些文件或者目录不添加到APK中,作用于APK,不能过滤aar和jar中的内容 + ```groovy + packagingOptions { + exclude 'META-INF/**' + exclude 'lib/arm64-v8a/libmediaplayer.so' + } + ``` +2. pickFirst:匹配到多个相同文件,只提取第一个。只作用于APK,不能过滤aar和jar中的文件 + ```groovy + packagingOptions { + pickFirst "lib/armeabi-v7a/libaaa.so" + pickFirst "lib/armeabi-v7a/libbbb.so" + } + ``` +3. doNotStrip:可以设置某些动态库不被优化压缩 + ```groovy + packagingOptions{ + doNotStrip "*/armeabi/*.so" + doNotStrip "*/armeabi-v7a/*.so" + } + ``` +4. merge:将匹配的文件都添加到APK中,和pickFirst有些相反,会合并所有文件 + ```groovy + packagingOptions { + merge '**/LICENSE.txt' + merge '**/NOTICE.txt' + } + ``` + +## 统一版本 + +应用由多个 module 构成,而不同地方引用的包,需要做到全局的统一时,可以创建一个 `xxx.gradle` 的文件(这里的 xxx,自行取一个表明含义的内容即可),然后在使用的地方时,统一调用定义的版本即可,使用步骤如下 + +1. 创建 `xxx.gradle` 文件(一般放在项目的根目录,和顶级 build.gradle 文件在同一层级),并添加如下内容,可根据自身需要调整 + ```groovy + ext { + + android = [ + compileSdkVersion: 29, + buildToolsVersion: "29.0.2", + minSdkVersion : 19, + targetSdkVersion : 29, + versionCode : 2020010102, + versionName : "0.1.0" + ] + + version = [ + androidSupportSdkVersion: "29.0.0", + retrofitSdkVersion : "2.6.3", + okhttpSdkVersion : "4.3.0", + dagger2SdkVersion : "2.22.1", + glideSdkVersion : "4.9.0", + butterknifeSdkVersion : "10.2.1", + rxlifecycle2SdkVersion : "2.2.1", + espressoSdkVersion : "3.0.2", + canarySdkVersion : "1.5.4" + ] + + // Android support 与 AndroidX support 对比 + // https://developer.android.google.cn/jetpack/androidx/migrate + + // support 库说明 + // https://developer.android.com/topic/libraries/support-library/features?hl=zh-cn + dependencies = [ + // support + "appcompat" : "androidx.appcompat:appcompat:1.1.0", + "annotations" : "androidx.annotation:annotation:1.0.0", + "cardview-v7" : "androidx.cardview:cardview:1.0.0", + "constraint-layout" : "androidx.constraintlayout:constraintlayout:1.1.3", + "swiperefreshlayout" : "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0", + "material" : "com.google.android.material:material:1.1.0", + "viewpager" : "androidx.viewpager:viewpager:1.0.0", + "recyclerview" : "androidx.recyclerview:recyclerview:1.1.0", + "vectordrawable" : "androidx.vectordrawable:vectordrawable:1.1.0", + "support-v4" : "androidx.legacy:legacy-support-v4:1.0.0", + + // network + "retrofit" : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}", + "retrofit-converter-gson" : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}", + "retrofit-converter-simplexml": "com.squareup.retrofit2:converter-simplexml:${version["retrofitSdkVersion"]}", + "retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}", + "okhttp3" : "com.squareup.okhttp3:okhttp:${version["okhttpSdkVersion"]}", + "okhttp3-logging-interceptor" : "com.squareup.okhttp3:logging-interceptor:${version["okhttpSdkVersion"]}", + "mockwebserver" : "com.squareup.okhttp3:mockwebserver:${version["okhttpSdkVersion"]}", + "glide" : "com.github.bumptech.glide:glide:${version["glideSdkVersion"]}", + // (annotationProcessor) + "glide-compiler" : "com.github.bumptech.glide:compiler:${version["glideSdkVersion"]}", + "glide-loader-okhttp3" : "com.github.bumptech.glide:okhttp3-integration:${version["glideSdkVersion"]}", + + // view + "butterknife" : "com.jakewharton:butterknife:${version["butterknifeSdkVersion"]}", + "butterknife-compiler" : "com.jakewharton:butterknife-compiler:${version["butterknifeSdkVersion"]}", + "brvah" : "com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.49-androidx", + "psid" : "com.oushangfeng:PinnedSectionItemDecoration:1.3.2-androidx", + "material-dialogs" : "com.afollestad.material-dialogs:core:2.8.1", + "material-input" : "com.afollestad.material-dialogs:input:2.8.1", + "material-files" : "com.afollestad.material-dialogs:files:2.8.1", + "material-color" : "com.afollestad.material-dialogs:color:2.8.1", + "material-datetime" : "com.afollestad.material-dialogs:datetime:2.8.1", + "pickerview" : "com.contrarywind:Android-PickerView:4.1.8", + "photoview" : "com.github.chrisbanes.photoview:library:2.0.0", + "lottie" : "com.airbnb.android:lottie:3.0.1", + "badge-view" : "q.rorbin:badgeview:1.1.3", + + // rx2 + "rxandroid2" : "io.reactivex.rxjava2:rxandroid:2.1.1", + "rxjava2" : "io.reactivex.rxjava2:rxjava:2.2.16", + // https://github.com/VictorAlbertos/RxCache + "rxcache2" : "com.github.VictorAlbertos.RxCache:runtime:1.8.3-2.x", + // https://github.com/tbruyelle/RxPermissions + "rxpermissions2" : "com.github.tbruyelle:rxpermissions:0.10.2", + + // tools(implementation) + "dagger2" : "com.google.dagger:dagger:${version["dagger2SdkVersion"]}", + "dagger2-android" : "com.google.dagger:dagger-android:${version["dagger2SdkVersion"]}", + "dagger2-android-support" : "com.google.dagger:dagger-android-support:${version["dagger2SdkVersion"]}", + "eventbus" : "org.greenrobot:eventbus:3.1.1", + "gson" : "com.google.code.gson:gson:2.8.5", + // https://projectlombok.org/setup/android + "lombok" : "org.projectlombok:lombok:1.18.8", + "multidex" : "com.android.support:multidex:1.0.3", + "arouter-api" : "com.alibaba:arouter-api:1.4.1", + "arouter-compiler" : "com.alibaba:arouter-compiler:1.2.2", + //(annotationProcessor) + "dagger2-compiler" : "com.google.dagger:dagger-compiler:${version["dagger2SdkVersion"]}", + "dagger2-android-processor" : "com.google.dagger:dagger-android-processor:${version["dagger2SdkVersion"]}", + + // test + "junit" : "junit:junit:4.12", + "androidJUnitRunner" : "androidx.test.runner.AndroidJUnitRunner", + "runner" : "androidx.test:runner:1.1.1", + "espresso-core" : "androidx.test.espresso:espresso-core:3.2.0", + "espresso-contrib" : "androidx.test.espresso:espresso-contrib:3.2.0", + "espresso-intents" : "androidx.test.espresso:espresso-intents:3.3.0", + "canary-debug" : "com.squareup.leakcanary:leakcanary-android:${version["canarySdkVersion"]}", + "canary-release" : "com.squareup.leakcanary:leakcanary-android-no-op:${version["canarySdkVersion"]}", + "umeng-analytics" : "com.umeng.analytics:analytics:6.0.1", + + // util + // https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/README-CN.md + "utilcode" : "com.blankj:utilcode:1.23.7", + + // help + "logger" : "com.orhanobut:logger:2.2.0", + // https://www.pgyer.com/doc/view/new_sdk_android_guide + "pgy" : "com.pgyersdk:sdk:3.0.3", + // SDK 包 + // https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613 + // https://jcenter.bintray.com/com/tencent/bugly/crashreport/ + "crashreport" : "com.tencent.bugly:crashreport:3.1.0", + // 升级 SDK 包 + // https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613 + // https://jcenter.bintray.com/com/tencent/bugly/crashreport_upgrade/ + "bugly-crash-upgrade" : "com.tencent.bugly:crashreport_upgrade:1.4.2", + // NDK 动态库 + // https://bugly.qq.com/docs/release-notes/release-android-ndk/?v=20180709165613 + // https://jcenter.bintray.com/com/tencent/bugly/nativecrashreport/ + "bugly-ndk" : "com.tencent.bugly:nativecrashreport:3.7.1", + + ] + } + ``` +2. 在顶级的 `build.gradle` 文件底部,表明添加对 `xxx.gradle` 的使用 + ```groovy + apply from: "xxx.gradle" + ``` +3. 在 module 级别的 `build.gradle` 文件中,修改哪些固定写死的依赖版本 + ```groovy + // 之前固定的版本 + minSdkVersion 19 + // 修改通过 xxx.gradle 中定义的版本 + minSdkVersion rootProject.ext.android["minSdkVersion"] + ``` + +## 参考 + +1. [Android Gradle 插件版本说明](https://developer.android.google.cn/studio/releases/gradle-plugin?hl=zh_cn) +2. [配置构建](https://developer.android.google.cn/studio/build?hl=zh_cn) +3. [配置构建变体](https://developer.android.google.cn/studio/build/build-variants?hl=zh_cn#product-flavors) diff --git a/source/_posts/gradle3.md b/source/_posts/gradle3.md new file mode 100644 index 000000000..73080aeb9 --- /dev/null +++ b/source/_posts/gradle3.md @@ -0,0 +1,157 @@ +--- +title: Gradle(三)SpringBoot 单工程 +date: 2020-12-16 13:30:46 +categories: Gradle +tag: [Gradle] +--- + +在 [Gradle(一)基础](https://incoder.org/2020/12/10/gradle1/) 的文章中,我们已经对 Gradle 有了一定的认识,本篇来看一看在后端开发中使用 Gradle 构建 SpringBoot 项目的开发。通常有两种方式来构建项目,第一种:每个功能模块即是一个代码工程,用一个 Git 仓库来管理,每个模块只负责完成一件事情;第二种:整个系统的多个模块聚合在一个代码工程里面,也就是我们常说的多模块项目,本篇先来讲一讲单工程 + + + +## 工程选择 + +对于单工程,和聚合工程的选择主要根据你所在项目团队的大小,项目分工,以及项目的复杂程度等来考虑。 + +单工程:适用于项目分工明确,项目庞大复杂,架构服务边界划分明确,配套的自动化等设施完善 +聚合工程:适用于项目人员不是很多,项目功能一般,需要一个人集中化管理等 + +## 环境 + +* OS:macOS 11.1 +* JDK:JDK1.8 +* Gradle:6.7.1-bin +* IDE:IntelliJ IDEA Community 2020.3 +* SpringBoot:2.4.1 + +## build.gradle + +```groovy +// 项目使用插件,可从 https://plugins.gradle.org 库中寻找合适的插件 +plugins { + id 'org.springframework.boot' version '2.4.1' + id 'io.spring.dependency-management' version '1.0.10.RELEASE' + id 'java' +} + +// 这里和 maven类似,用于项目唯一坐标 +group = 'com.example' +version = '0.0.1-SNAPSHOT' +// 项目兼容版本 +sourceCompatibility = '1.8' + +// 依赖第三方jar从哪个仓库去下载 +repositories { + mavenCentral() +} + +// 项目所需的第三方依赖 +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +// 测试相关 +test { + useJUnitPlatform() +} +``` + +一个 SpringBoot 项目基本的 build.gradle 文件由 plugins,项目坐标,repositories,dependencies,test 基础内容组成。关于 plugins 使用常见有两种方式,核心的依赖,是没有版本号,它和你使用的 Gradle 关联,你无需过多关系这些核心插件的依赖版本 + +```groovy +// 旧方式 +apply plugin: 'java' + +// 新方式(推荐) +plubins { + id 'java' +} +``` + +## settings.gradle + +用于项目模块管理,由于这个单工程,这里只有一个模块 + +```groovy +rootProject.name = 'demo' +``` + +## 多环境 + +可通过自定义 task 来出来 + +```groovy +// prod +tasks.register("bootRunProd") { + group = "application" + description = "Runs the Spring Boot application with the prod profile" + doFirst { + tasks.bootRun.configure { + systemProperty("spring.profiles.active", "prod") + } + } + finalizedBy("bootRun") +} + +// dev +tasks.register("bootRunDev") { + group = "application" + description = "Runs the Spring Boot application with the dev profile" + doFirst { + tasks.bootRun.configure { + systemProperty("spring.profiles.active", "dev") + } + } + finalizedBy("bootRun") +} +``` + +启动方式 + +* 方式一:图形化界面中,直接运行对应环境 + ![](https://res.cloudinary.com/incoder/image/upload/v1609691501/blog/gradle-task-gui.png) +* 方式二:在命令行中,使用命令来运行对应环境,比如 `gradlew bootRunDev` + ![](https://res.cloudinary.com/incoder/image/upload/v1609691543/blog/gradle-task-terminal.png) + +* 方式三:当然你也可以在启动时指定你需要激活的环境 + ```bash + # 这里激活的 test 环境,把 ${jar_name} 参数换成对应启动的应用文件 + java -jar ${jar_name} --spring.profiles.active=test + ``` + +## 排除依赖 + +```groovy +testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' +} +``` + +## 打包 + +打包时需要,注意我们的 SpringBoot 应用它本质上是一个 bootJar(Fatjar) 应用,因此需要将应用打成一个 bootJar(Fatjar)。而对于什么是 bootJar 和 jar 的区别,可以查看之前在 SpringBoot(二) 启动分析JarLauncher 文章中对于 [jar 规范](https://incoder.org/2019/07/05/springboot2/#jar%E8%A7%84%E8%8C%83) 说明 + +打包方式 + +* 方式一:图形化操作 + +* 方式二:命名执行 + ```bash + # 在项目的根目录执行,Windows 使用:gradlew;Linux/macOS:./gradlew + # 当然如果那你已安装且配置好 gradle 的环境,你可以直接使用 gradle 代替 ./gradlew 的相关命令 + gradlew bootJar + ``` + +## 发布 + + + +## 参考 + +1. [SpringBoot+gradle 构建多模块项目](https://blog.csdn.net/formularoom/article/details/70354562) +2. [IDEA 2020.2 + Gradle 6.6.1 + Spring Boot 2.3.4 创建多模块项目](https://blog.csdn.net/zh452647457/article/details/108844078) +3. [Spring-boot 2.3.x 源码基于Gradle编译](https://blog.csdn.net/buhuiguowang/article/details/110700585) +4. [用 Gradle 构建 Spring Boot 项目](https://www.cnblogs.com/davenkin/p/gradle-spring-boot.html) +5. [使用 Gradle 构建 springboot 多模块项目,并混合groovy开发](https://www.cnblogs.com/houzheng/p/11024865.html) +6. [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/) \ No newline at end of file diff --git a/source/_posts/granddad.md b/source/_posts/granddad.md new file mode 100644 index 000000000..0ffea34d8 --- /dev/null +++ b/source/_posts/granddad.md @@ -0,0 +1,29 @@ +--- +title: 愿天堂没有病痛 +date: 2022-06-15 06:15:00 +categories: 纪念 +hidden: true +tag: [Top] +--- + +仅以此篇文章记录,我对爷爷的思念,愿天堂没有病痛…… + + + +时间仿佛定格在这一天了,一向最不留情面的爷爷,在寂静的夜晚,悄悄地离开了我们。虽然对于爷爷感情并不是很深,但或多或少在我心里有不曾抹去的对与我的爱,也许只是在我看来是那么的保守和不近人情,可能这只是您独有的关爱方式罢了。 + +不经想起上一次见面,是在上一次回家(2020 年劳动节假期),去探望了您,没想到这一见面竟然是天人永隔。您的连环追命问题,迫使我是连连败退,不敢轻易登上您的大门。听爸妈说,今年您身体不好,一直住在医院,原本计划等今年十一左右回家,然后再去看看您,这成了永远无法完成的计划😭。姐姐哭着告诉我想到的事情就要去做,不要等到了失去后才后悔莫及,是呀,人总是等到失去后才懂得珍惜,我只是感觉越是长大越是难以想到同时也能做到 + +爷爷在那个时代属于中等个头,身材在我看来不胖不瘦,家里有琳琅满目各式各样稀奇古怪的东西,有手动给木头打孔的转,刨木头的刨子,标记线的墨斗还有好多好多我不知道的工具一副活脱脱的木工装备,但实际上爷爷并不是木工,只知道爷爷是有单位的人,是有文化的知识分子,在县城有单位分房子,在我印象中有三件事我记忆深刻 + +第一件,小时候去您家玩,在桌子上看到了各式各样的桥梁图纸,不知道这些桥梁都修建在哪里,然后看到了有一个白色透明很高级的三角板,当然我也见到了不一样的东西,一种被称之为 “雷管” 的炸药,这些应该都是您当年工作中经常使用的一些工具吧,在现在来看应该属于土木工程系,后来上小学,那把三角尺成了小学生涯最长的使用工具。如果有时光机,我想听一听您那时候工作故事和那些作图技巧,还想去看看那些您曾经设计或建造的桥梁道路 + +第二件,有一年过春节,爸爸给我买了枪(那个时候,男孩子过年好像都少不了这个礼物),然后到您的住的地方,您说这个样式不好,一连就在您家前门店家那里换了 3 次,我现在也记不清那是一把什么样的枪,好像记得一按斑鸠就会有音乐,在那个时候很高级的样子,如果有时光机,我想把那是我的玩具收藏进我的百宝箱里 + +第三件,那时候我小学六年级还是刚上初中吧。那一年大姑,小姑带着弟弟,妹妹从上海回到老家,然后在我们在您的台球厅玩耍,我学会了小台球的游戏规则,没人的时候我们会一起玩耍,可我重来都没有赢过一场,看来我对这项运动没有任何天赋,如果有时光机,我想和您再打一场台球 + +从我的视角来看,随着慢慢的长大到工作,相见机会越来越少,有时候一年也没有见上一面。刚开始工作后偶尔会打电话给你,到后来几乎没有再打过电话给你,因为每次电话我都是扯着嗓子在那喊,你说你才能听清我在说什么,其次本身自己是一个内向的人,话也很少,平时也没啥话可说,就这样渐渐几乎是失去了联系,每次也都是听父母的口中知道您的近况 + +虽然在我的记忆里除了这些温暖的画面,还有不少您的负面形象,可现在这些都不重要了,毕竟这些也是你生命里的一部分。我没有经历过,我没有发言权,就让这些负面的内容随风而去吧。记着一个人的好好过记住一个人的坏,身在异地他乡的我不能及时的回到你身边为你送上最后的一程,深感惭愧😮‍💨 + +爷爷您一路走好,愿天堂里没有病痛 \ No newline at end of file diff --git a/source/_posts/hexo-advanced.md b/source/_posts/hexo-advanced.md new file mode 100644 index 000000000..17920a41e --- /dev/null +++ b/source/_posts/hexo-advanced.md @@ -0,0 +1,239 @@ +--- +title: Hexo Blog 高级指南 +date: 2020-11-20 18:18:18 +categories: Hexo +tag: Build +--- + +[NexT](https://theme-next.js.org) 是 [Hexo](https://hexo.io/zh-cn/index.html) 非常受欢迎的博客主题,方便简洁,但却不简单的功能,你可以在提供的强大功能基础上进行扩展或者自定义,来满足你的个性化需求。本篇文章主要是对应 NexT 提供的一些高级功能的使用,作为一个持续更新的文章吧,记录自己 SX 操作,当然也是我平时在使用 NexT 时遇到的一些问题的记录。好了废话不多说了,我们直接进入正题 + + + +## 博客升级 + +每次对于 NexT 的升级或多或少都会遇到些问题,这次也不例外,首先是对于不同版本的管理,由于一些历史原因有三个组织仓库分别对应不同的版本域,升级是需要注意下,本次我是从 7.8.0 版本升级到 8.0.x 版本,以后跟随官方,每月更新 NexT + +### npm 改成 yarn(可选) + +>yarn 的安装,请自行根据你的系统去安装,我这里 macOS 使用命令即可 `brew install yarn` + +1. 删除根目录的 `package-lock.json`,并在根目录执行 `hexo clean && rm -rf node_modules/` +2. 根目录下执行 `yarn install` + +### 更改 NexT 主题仓库 + +1. 删除当前主题,在根目录下执行 `rm -rf themes/` +2. 安装新的主题, + * 方案一:在根目录下执行命令添加主题 + ```bash + git clone https://github.com/next-theme/hexo-theme-next themes/next + ``` + * 方案二:通过 yarn 来管理主题 + ```bash + yarn add hexo-theme-next + ``` + +### 修改配置 + +之前为了使主题更新不受影响,在项目的根目录 `source/_data` 路径下有一个 `next.yml` 文件来进行对 NexT 的自定义设置,那么在 8.0 版本开始,在项目根目录 `_config.{theme}.yml` 文件来代替之前在 `source/_data` 路径下的 `next.yml` 文件 + +### 问题 + +#### node --trace-warnings + +##### 异常信息 + +由于 NexT 需要 Hexo5.0+,在升级到 NexT 8.0.x 版本警告信息如下 + +```log +(node:17336) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency +(Use `node --trace-warnings ...` to show where the warning was created) +(node:17336) Warning: Accessing non-existent property 'column' of module exports inside circular dependency +(node:17336) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency +(node:17336) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency +(node:17336) Warning: Accessing non-existent property 'column' of module exports inside circular dependency +(node:17336) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency +``` + +##### 原因分析 + +是由于 Hexo 项目嵌套依赖了 `stylus` 包,而对于 `0.54.5` 版本在 Node 14+ 版本存在问题,比如这里 `hexo-renderer-stylus` 包的依赖 + +``` +…… +│ │ +├─┬ hexo-renderer-stylus@2.0.1 +│ ├─┬ nib@1.1.2 +│ │ └─┬ stylus@0.54.5 +│ │ │ +…… +``` + +##### 解决方法 + +1. 可以降低你的 Node 版本到 12 版本 + ```bash + brew uninstall node + brew install node@12 + brew link --overwrite --force node@12 + ``` +2. 推荐,更改替换 stylus 版本,在你的 `package.json` 文件中,添加如下配置 + ```json + "resolutions": { + "stylus": "^0.54.8" + } + ``` + +##### 总结 + +🌀 [pull-2538](https://github.com/stylus/stylus/pull/2538) +🐞 [issues-2534](https://github.com/stylus/stylus/issues/2534) +🛠 [solve-Accessing non-existent property](https://www.haoyizebo.com/posts/710984d0/) + +#### hexo-douban + +之前用了 hexo-douban 插件来进行对 books 和 movies 进行管理,在升级到 Node 14+版本上,当前的插件也停止工作了,异常日志如下 + +```log +INFO 0 books have been loaded in 1130 ms, because you are offline or your network is bad +INFO 0 movies have been loaded in 1329 ms, because you are offline or your network is bad +INFO 0 games have been loaded in 1004 ms, because you are offline or your network is bad +``` + +作者在🐞 [issues-2534](https://github.com/mythsman/hexo-douban/issues/77) 做了回复,暂时没有替代方案,故在新版中,我停止了 `hexo-douban` 插件的使用,挖个坑,等自己有时间或者有人修复此问题再或者有替代插件后再重新启用 + +1. 移除 hexo-douban 插件 + ```bash + # yarn + yarn remove hexo-douban + # npm + npm uninstall hexo-douban + ``` +2. 移除 `_config.yml` 配置文件中,douban 的相关的配置 +3. 移除 `_config.{theme}.yml` 配置文件中,`menu` 配置的站点入口设置 + +## 博客评论 + +在 NexT version 8.1.0 版本,由于安全问题,[Valine被移除](https://github.com/next-theme/hexo-theme-next/issues/4#v8.1.0%20%E7%A7%BB%E9%99%A4%20Valine),~~暂时我并未迁移 Valine 的评论~~ + +博客已启用 [utterances](https://utteranc.es) 评论支持,配置也比较简单,如下 + +```yml +utterances: + enable: true + repo: BladeCode/BladeCode.github.io # Github repository name + # Available values: pathname | url | title | og:title + issue_term: title + # Available values: github-light | github-dark | preferred-color-scheme | github-dark-orange | icy-dark | dark-blue | photon-dark | boxy-light + theme: github-light +``` + +## 文章加密 + +对于 NexT 的文章,有时需要进行加密访问,那么该怎么去处理呢,其实这一点在 NexT 的生态里已经有了这样的插件,我们可以直接在使用在我们的 NexT 里面,只需要简单的配置 + +```bash +# npm +npm i hexo-blog-encrypt --save +# yarn +yarn add hexo-blog-encrypt +``` + +加密优先级:文章信息头 > 按标签加密 + +### 站点配置(_config.yml) + +#### 简单配置 + +```yml +# 文章密码访问 hexo-blog-encrypt +encrypt: + enable: true +``` + +#### 更多配置 + +可以对一类(标签)来进行统一的密码设置 + +```yml +# 文章密码访问 hexo-blog-encrypt +encrypt: + abstract: 有东西被加密了, 请输入密码查看. + message: 您好, 这里需要密码. + tags: + - {name: tagNameA, password: 密码A} + - {name: tagNameB, password: 密码B} + template:
+ wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试. + wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容. +``` + +### 单文章配置 + +在你需要加密的文章前面,根据需要添加对应的参数,这里仅是一个示例 + +```markdown +--- +title: Hello World +tags: +- 加密文章tag +date: 2020-11-20 18:18:18 +password: helloworld +abstract: 该文章已加密, 请输入密码查看。 +message: 该文章已加密, 请输入密码查看。 +wrong_pass_message: 密码不正确,请重新输入! +wrong_hash_message: 文章不能被校验, 不过您还是能看看解密后的内容! +--- +``` +各参数说明 +* password:文章密码 +* abstract:文章摘要,会显示在博客的列表页 +* message:文章查看时,密码输入框上面的描述性文字 +* wrong_pass_message:校验失败提示 +* wrong_hash_message:hash 验证失败 + +## 多语言 + +对于多语言,根据自身需要添加,默认,修改博客项目根目录 `_connfig.yml` 文件 `language` 属性即可 +1. 对于单语言:language: xxx(具体语言可查看下方的官方说明) +2. 对于多语言: + * 语言添加 + ```yml + language: + - zh-CN + - en + ``` + * 更改语言切换,`_config.{theme}.yml` 文件,`language_switcher`设置为 true +3. 字段定义,如果一些字段的翻译不是你想要的,你可以自行修改 + * 在根目录的 `source/_data` 文件夹下,创建 `languages.yml` 文件 + * 在文件中,修改对应语言的字段 + ```yml + zh-CN: + # items + post: + copyright: + # the translation you perfer + author: 本文博主 + en: + menu: + schedule: Calendar + ``` + +>[多语言配置](https://theme-next.js.org/pisces/docs/theme-settings/internationalization.html?highlight=language) + +## GitHub Action + +## Hexo PWA + +>由于暂未支持 Hexo5.0+版本,先占坑 + +## 参考 + +* [更新说明及常见问题](https://github.com/next-theme/hexo-theme-next/issues/4) +* [将 Hexo 升级到 v5.0.0](https://tommy.net.cn/2020/08/08/upgrade-hexo-to-v5-0-0/) +* [用 GitHub Actions 来自动部署 Hexo](https://tommy.net.cn/2020/08/06/deploy-hexo-with-github-actions/) +* [Hexo博客部署PWA](https://linwhitehat.github.io/Blog/2020/02/09/Hexo%E5%8D%9A%E5%AE%A2%E9%83%A8%E7%BD%B2PWA.html) +* [博客完美支持 PWA](https://sitoi.cn/posts/49115.html) +* [三步,让 Hexo 轻松支持 PWA](https://blog.decay.fun/2019/08/19/enhance-hexo-with-pwa-in-three-steps/) +* [Pwabuilder](https://www.pwabuilder.com/) +* [Hexo 相关问题和优化](https://blog.csdn.net/qq_32767041/article/details/103285147) diff --git a/source/_posts/hexo-blog.md b/source/_posts/hexo-blog.md new file mode 100644 index 000000000..2d4e8607d --- /dev/null +++ b/source/_posts/hexo-blog.md @@ -0,0 +1,144 @@ +--- +title: Hexo Blog 搭建 +date: 2018-03-25 01:18:26 +categories: Hexo +tag: Build +--- + +之前一直纠结用[Jekyll](https://jekyllrb.com)还是[Hexo](https://hexo.io)来搭建[GitHub Page](https://pages.github.com)博客,原本一直想搭建一个[Material Design](https://material.io/guidelines)主题风格,从[Hexo Themes](https://hexo.io/themes)中寻找到一款不错的主题,[indigo](https://github.com/yscoder/hexo-theme-indigo)是一款支持IE10+,评论,目录导航,分享等功能的轻量Blog主题。 + +简单的修改了该主题之后,本地预览都没有什么问题,但是部署到[Github]()上,样式什么的都无法加载,应该是我的操作姿势不对吧,调整了半天没有解决,烦躁中找到之前star的另一款很受欢迎的[Next](https://github.com/iissnan/hexo-theme-next)主题。 + +既然自己修改的无法正常部署预览,那就用别人写好的吧,刚好赶上[Next](https://github.com/theme-next/hexo-theme-next)新版本V6.0系列的推出,那就不废话,直接开干 + + + +## 材料准备 + +* [Node LTS](https://nodejs.org/en/download) +* [Git](https://git-scm.com/downloads) +* [Hexo](https://hexo.io) +* [Next](https://github.com/theme-next/hexo-theme-next) + +## 安装 + +`Node`,`Git`的安装过程略 + +### Hexo + +1. Hexo 安装 + ``` bash + $ npm install hexo-cli -g + ``` +2. 初始化 + ``` bash + $ hexo init + ``` +3. 安装依赖包 + ``` bash + $ cd + $ npm install + ``` +4. 启动服务预览 + ``` bash + $ hexo serve + ``` + +### Next + +1. 安装Next 主题 + ``` bash + $ git clone https://github.com/theme-next/hexo-theme-next themes/next + ``` + > 当前操作在 `blog`的根目录下执行 + +2. 修改Blog 配置 +`you blog name` 根目录 `_config.yml` + * theme: 由原来默认`landscape`更改位`next`(大约:76行) + * 其他配置项,根据自己的需求进行更改,我这里更改了`title`,`subtitle`,`author`,`language`,`url`配置,其中`language`如果没有修改,默认为英文语言,在V6.0系列由原来`zh-Hans`更新为`zh-CN` + * 添加部署到Github配置 + ``` bash + deploy: + type: git + repo: https://github.com/BladeCode/BladeCode.github.io.git # 用户名仓库 + branch: master # 用户名仓库的分支应该指定master,master分支也可以不用写 + ``` + +3. 修改Theme 配置 +路径:`you blog name`/Themes/next/_config.yml +这里不罗嗦了,其配置可参考[hexo-theme-next](https://github.com/iissnan/hexo-theme-next)项目`README`文件 + +### 部署 + +上面已经配置好了部署的目标仓库,那么这里直接使用Hexo提供的部署命令即可 +``` bash +$ hexo d +``` +相关命令介绍等,请查看[官方文档说明](https://hexo.io/docs) + +部署完成后,可以直接访问 http://`you blog name`/github.io + +## 自定义域名 + +虽然现在 blog 可以使用 Github 提供的项目二级域名来访问,为了个性化以及方便等,配置自己的域名 +1. 登录域名所属的管理网站(这里以阿里云域名服务为例) + ![gitpages-domain-manger](https://res.cloudinary.com/incoder/image/upload/v1525516603/blog/gitpages-domain-manger.png) +2. 添加解析 + ``` bash + $ # 解析一 + 记录类型:CNAME + 主机记录:www + 记录值:bladecode.github.io + 解析路线:default + + $ # 解析二 + 记录类型:A + 主机记录:@ + 记录值:192.30.252.153 + 解析路线:default + + $ # 解析三 + 记录类型:A + 主机记录:@ + 记录值:192.30.252.154 + 解析路线:default + ``` + > 192.30.252.153是GitHub的地址,你也可以ping你的 http://xxxx.github.io 的ip地址,填入进去 + +3. 修改Github上项目的domain设置 + ![gitpages-domain-custom](https://res.cloudinary.com/incoder/image/upload/v1525516630/blog/gitpages-domain-custom.png) +4. 添加CNAME文件 +保存路径:`you blog name`/source +新增文件:CNAME 文件 (格式要求:`保存成所有文件而不是txt文件`) +CNAME 文件内容:`incoder.org` +> 如果带有www,那么以后访问的时候必须带有www完整的域名才可以访问,但如果不带有www,以后访问的时候带不带www都可以访问。所以建议,不要带有www + +## Https开启 + +开启Https 需要借助[Cloudflare](https://www.cloudflare.com),关于Cloudflare的介绍等不在这里展开 +1. 注册账号 +2. Add website + ![site](https://res.cloudinary.com/incoder/image/upload/v1525516650/blog/gitpages-https-add-site.png) +3. Querying your DNS + ![query](https://res.cloudinary.com/incoder/image/upload/v1525516664/blog/gitpages-https-dns-query.png) +4. Select Plan + ![plan](https://res.cloudinary.com/incoder/image/upload/v1525516681/blog/gitpages-https-select-plan.png) +5. 域名解析记录获取 + ![continue](https://res.cloudinary.com/incoder/image/upload/v1525516694/blog/gitpages-https-continue.png) +6. DNS 对比,并修改[Cloudflare]()提供的DNS来解析 + ![change](https://res.cloudinary.com/incoder/image/upload/v1525516714/blog/gitpages-https-change-dns.png) +7. 域名管理后台,修改DNS + ![dns](https://res.cloudinary.com/incoder/image/upload/v1525516733/blog/gitpages-https-wanwang-dns.png) + > 阿里云服务相关域名DNS修改帮助[文档](https://help.aliyun.com/knowledge_detail/39844.html) +8. 成功激活 + ![active](https://res.cloudinary.com/incoder/image/upload/v1525516756/blog/gitpages-https-active.png) +9. SSL证书申请提醒 + ![cer](https://res.cloudinary.com/incoder/image/upload/v1525516994/blog/gitpages-https-ssl-cer.png) +10. 添加强制HTTPS规则 + ![rule](https://res.cloudinary.com/incoder/image/upload/v1525517025/blog/gitpages-https-page-rule.png) +11. 规则制定 + ![deploy](https://res.cloudinary.com/incoder/image/upload/v1525517045/blog/gitpages-https-deploy-https.png) + +好了剩下的就是等证书颁发,可能要等上一些时间,具体每个人不尽相同,这里就不多做解释了。 + +Let's all,本次的Hexo的相关初级教程就到这里 diff --git a/source/_posts/hexo-iterative.md b/source/_posts/hexo-iterative.md new file mode 100644 index 000000000..646c2cf95 --- /dev/null +++ b/source/_posts/hexo-iterative.md @@ -0,0 +1,129 @@ +--- +title: Hexo Blog 迭代 +date: 2018-05-02 18:18:18 +categories: Hexo +tag: Build +--- + +最初博客通过[Cloudflare](https://www.cloudflare.com)反向代理进行HTTPS解析,放完五一假期,Github官方开始支持[自定义域名的HTTPS解析](https://blog.github.com/2018-05-01-github-pages-custom-domains-https),在使用Cloudflare期间,经常性的521等问题烦恼,这次也可以名正言顺的弃用CloudFlare + +**本次迭代内容** +* 弃用Cloudflare +* 自动化部署 +* 常用设置 +* 常用插件安装 + + + +## 弃用Cloudflare + +1. 关闭Cloudflare中设置Page Rules +2. 删除Cloudflare的DNS记录 +3. 还原域名配置中的DNS解析 +4. 添加Github提供的IP解析 + +[官方自定义域名设置](https://help.github.com/articles/setting-up-an-apex-domain/#configuring-an-alias-or-aname-record-with-your-dns-provider) + +## 自动化部署 + +>[Github Pages](https://pages.github.com)是Github 提供一个渲染静态的Web页面服务 +* `{username}.github.io`仓库默认`master`分支 +* 其他项目仓库,默认`gh-pages`分支 +* [官方说明文档](https://help.github.com/articles/user-organization-and-project-pages) + +因此`{username}.github.io`仓库,dev分支用来存储网站的源码,`master`分支存放生成的静态文件,这样一个仓库就可以管理整个项目。每次`push`新的功能,然而每次都需要先`push`到`dev`分支,然后生成静态文件,再`push`到`master`分支,这种重复性的操作,实在太不优雅,所以采用[Travis CI](https://travis-ci.org)进行自动化部署 + +接着Github支持自定义域名开启HTTPS的好消息,Travis CI (https://travis-ci.com) 也支持开源项目啦 + +> Travis CI 区别 + +* Travis-CI(https://travis-ci.org) :GitHub公开项目 +* Travis-CI(https://travis-ci.com) :~~私有付费项目~~,[2018.05.02也开始支持开源项目](https://blog.travis-ci.com/2018-05-02-open-source-projects-on-travis-ci-com-with-github-apps?utm_source=Broadcast&utm_campaign=2may_release) + +[GitHub Services are being deprecated](https://developer.github.com/changes/2018-04-25-github-services-deprecation),因此本节的自动化部署就开启Travis CI (https://travis-ci.com) 集成方案 + +### 准备 + +1. 使用GitHub账号登录Travis-CI,并确认接受访问 +2. 同步了GitHub存储库,转到您的配置文件页面并启用您想要构建的存储库 +3. 添加 `.travis.yml` 文件到构建部署项目的根目录下 + +### Hexo 自动部署 + +部署流程 +![部署流程](https://res.cloudinary.com/incoder/image/upload/v1525517765/blog/gitpages-travis-ci-branch-deploy.png) + +Hexo 部署脚本示例 +```bash +# 设置语言 +language: node_js +# 设置相应的版本 +node_js: + - '12.16.3' + # - lts/* +# 可以减少travis构建时间 +cache: + directories: + - node_modules +before_install: + # - npm config set bin-links false + # - npm install -g hexo + - npm install -g hexo-cli +# 安装hexo及插件 +install: + - npm install +before_script: + - npm install -g mocha + - git clone --branch master https://github.com/BladeCode/BladeCode.github.io.git public +script: + # 清除 + - hexo cl + # 生成 + - hexo g +after_script: + - cd ./public + - git init + # 修改成自己的github用户名 + - git config user.name "BladeCode" + # 修改成自己的GitHub邮箱 + - git config user.email "Jerry.x@outlook.com" + - git add . + - git commit -m "update by Travis-CI on `date '+%Y-%m-%d %H:%M:%S'`" + # GH_token就是在travis中设置的token + - git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:master +branches: + only: + # 只监测这个分支,一有动静就开始构建 + - dev +env: + global: + # 设置仓库地址 + - GH_REF: github.com/BladeCode/BladeCode.github.io.git + +``` + +## 常用设置 + +[NexT 配置使用手册](http://theme-next.iissnan.com) +[NexT 配置使用手册 {% label primary@新 %}](https://theme-next.js.org/docs/) + +### NexT主题更新 + +[官方说明](https://github.com/theme-next/hexo-theme-next/blob/master/docs/zh-CN/DATA-FILES.md) + +## 常用插件安装 + +* 文章字符统计 [hexo-symbols-count-time](https://github.com/theme-next/hexo-symbols-count-time) +* 修复LeanCloud访客计数器中的严重安全漏洞 [hexo-leancloud-counter-security](https://github.com/theme-next/hexo-leancloud-counter-security) +* 图片灯箱 [theme-next-fancybox3](https://github.com/theme-next/theme-next-fancybox3) +* 本地检索 [hexo-generator-searchdb](https://github.com/theme-next/hexo-generator-searchdb) +* 注脚 [hexo-renderer-markdown-it-plus](https://github.com/CHENXCHEN/hexo-renderer-markdown-it-plus) +* 文章加密 [hexo-blog-encrypt](https://github.com/D0n9X1n/hexo-blog-encrypt) + +## 其他 + +### 图床选择 + +* [个人网站中的静态文件云存储选择](https://jimmysong.io/posts/static-website-storage) +* [嗯,图片就交给它了](https://sspai.com/post/40499) +* [NexT主题无法备份解决方式](https://github.com/iissnan/hexo-theme-next/issues/932) diff --git a/source/_posts/hugo.md b/source/_posts/hugo.md new file mode 100644 index 000000000..e64de7af4 --- /dev/null +++ b/source/_posts/hugo.md @@ -0,0 +1,156 @@ +--- +title: Hugo 初体验 +date: 2018-07-11 16:34:10 +categories: Hugo +tag: Build +--- + +个人博客使用 [Hexo](https://hexo.io/zh-cn/index.html) 搭建,使用效果很不错,[RootCluster](https://github.com/RootCluster) 组织主要存放自己新技术的学习和一些Demo实验。该组织同样也可以使用Github pages服务,因此也需要给RootCluster构建一个静态页面,可用直观清晰的看自己的项目,虽然之前已使用Hexo构建,为了了解其他的静态页面构建,所以这次选择了 [Hugo](https://gohugo.io) + +[Hugo](https://gohugo.io) 是世界上最快的静态网站引擎。它是用 [Go](https://golang.org)(aka Golang)编写的,由 [bep](https://github.com/bep),[spf13](https://github.com/spf13) 和[朋友](https://github.com/gohugoio/hugo/graphs/contributors)开发 + + + +## 材料准备 + +* SystemOS:Windows 10 +* [Chocolatey](https://chocolatey.org):Windows的包管理器 +* [Hugo](https://gohugo.io/getting-started/installing) + +## 安装 + +### Chocolatey安装 + +如果已安装,跳过该步骤 + +* 使用 PowerShell.exe + ```bash + Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) + ``` +* 使用 cmd.exe + ```bash + @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" + ``` +以上两种方式,选择其一即可 + +![PowerShell.exe 演示](https://res.cloudinary.com/incoder/image/upload/v1531314279/blog/hugo_install.png) + +### hugo安装 + +```bash +choco install hugo -confirm +``` + +### 初始化Hugo + +* 初始化hugo模板 + ```bash + hugo new site project_name + ``` +* 进入项目并启动项目 + ```bash + cd project_name && hugo serve + ``` + ![hugo_init](https://res.cloudinary.com/incoder/image/upload/v1531314737/blog/hugo_init.png) + +* [主题安装](https://themes.gohugo.io) + + 这里选择[Elate](https://themes.gohugo.io/hugo-elate-theme)主题作为组织的网站 + + ![](https://res.cloudinary.com/incoder/image/upload/v1531316293/blog/hugo_theme.png) + +## GitHub Action 部署 + +1. 新生成部署 key + ```shell + # 1. 进入本地电脑的 .ssh 文件夹 + cd .ssh/ + # 2. 生成部署 key + ssh-keygen -t rsa -b 4096 -C "Jerry.x@outlook.com" -f id_rsa_deploy -N "" + ``` +2. 添加部署 key 到项目仓库设置中 + +```yaml +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: master + pull_request: + branches: dev + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + with: + submodules: true + fetch-depth: 0 + # Runs a single command using the runners shell + - name: Setup Hugo + uses: peaceiris/actions-hugo@v2.3.1 + with: + hugo-version: '0.61.0' + # Runs a set of commands using the runners shell + - name: Build + run: hugo --minify + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.ACTIONS_DEPLOY_KEY }} + publish_dir: ./public + commit_message: ${{ github.event.head_commit.message }} +``` + +### Travis + +```yml +language: go + +go: + - master # 使用最新版本 + +# Specify which branches to build using a safelist +# 分支白名单限制: 只有hugo分支的提交才会触发构建 +branches: + only: + - dev + +install: +# 安装最新的hugo + - go get -v github.com/gohugoio/hugo + +script: +# 运行hugo命令 + - hugo + +deploy: + provider: pages # 重要,指定这是一份github pages的部署配置 + skip-cleanup: true # 重要,不能省略 + local-dir: public # 静态站点文件所在目录 + target-branch: master # 要将静态站点文件发布到哪个分支 + github-token: $GITHUB_TOKEN # 重要,$GITHUB_TOKEN是变量,需要在GitHub上申请、再到配置到Travis + keep-history: true # 是否保持target-branch分支的提交记录 + on: + branch: dev # 博客源码的分支 +``` + +## 参考 + +1. [Hugo Documentation](https://gohugo.io/documentation/) +2. [Hugo setup](https://github.com/marketplace/actions/hugo-setup) \ No newline at end of file diff --git a/source/_posts/idea-multi-module.md b/source/_posts/idea-multi-module.md new file mode 100644 index 000000000..cf8a196f0 --- /dev/null +++ b/source/_posts/idea-multi-module.md @@ -0,0 +1,98 @@ +--- +title: IDEA 多模块项目 +date: 2019-01-10 00:32:10 +categories: IDEA +tag: [SpringBoot, Init] +--- + +[Jetbrains](https://www.jetbrains.com)系列中[IDEA](https://www.jetbrains.com/idea)是现如今公认最好用,最强大的Java开发工具,不接受任何反驳,本篇介绍macOS上使用 IDEA 创建 SpringBoot 多模块项目 + +## 准备工作 +* 系统环境:macOS 10.14.2 +* 应用工具:[IDEA](https://www.jetbrains.com/idea),[Maven](https://maven.apache.org) + +>这里不再介绍基本软件的安装及配置 + + + +## 多模块项目 + +一般简单的项目,按照如下项目结构进行构建,可根据也无需要自行调整 +``` +rc-springboot-docker + ├── boot-api # 项目对应用服务间提供api的接口,同时也管理项目常量、REST返回组装实体类等 + ├── boot-common # 项目公共基础包(可丢弃) + ├── boot-core # 项目业务操作,server dao层 + ├── boot-web # 项目后端Web管理 + ├── boot-rest # 项目业务控制层,给客户端提供rest接口 + └── README.md +``` + +* boot-api:是一个maven module +* boot-common:是一个maven module +* boot-core:是一个maven module +* boot-web:是一个springboot module +* boot-rest:是一个springboot module + +## 构建 + +### Parent Project +顾名思义,这是项目的外壳,一个标准的empty maven project,当然你要可以使用gradle来作为项目的构建工具,可根据自身需要自行选择,这里采用maven方式演示 +* `Create Project` + ![idea-new-project](https://res.cloudinary.com/incoder/image/upload/v1547062782/blog/idea-new-project.png) +* 设置项目groupId和artifactId等信息 + ![idea-new-setting](https://res.cloudinary.com/incoder/image/upload/v1547062780/blog/idea-new-setting.png) +* 设置项目名称及项目存储位置 + ![idea-new-path](https://res.cloudinary.com/incoder/image/upload/v1547062780/blog/idea-new-path.png) +* 删除项目src目录,使项目成为名副其实的空项目 + ![idea-delete-src](https://res.cloudinary.com/incoder/image/upload/v1547062780/blog/idea-delete-src.png) +* 新增忽略文件 + ![idea-new-ignore](https://res.cloudinary.com/incoder/image/upload/v1547062782/blog/idea-new-ignore.png) + 新增忽略文件的目的: + 1. 忽略项目中不需要进行版本追踪的文件 + 2. 隐藏忽略文件 + +* 选择maven项目模板忽略文件 + ![idea-select-maven](https://res.cloudinary.com/incoder/image/upload/v1547062782/blog/idea-select-maven.png) +* 修改忽略文件及隐藏忽略文件 + ![idea-ignore-settings](https://res.cloudinary.com/incoder/image/upload/v1547062781/blog/idea-ignore-settings.png) + ``` + # IntelliJ project files + .DS_Store + .idea/ + *.iml + out + gen + + # eclipse + *.classpath + *.project + *.springBeans + ``` + >关于ignore文件的写法,可以参考[.gitignore 基础知识](https://incoder.org/2018/04/13/gitignore/) + +### Module Project +在module中有两类,一类是maven项目,还有一类是需要启动的springboot项目 + +#### maven module project + +* 创建maven module + ![idea-module-maven](https://res.cloudinary.com/incoder/image/upload/v1547066735/blog/idea-module-maven.png) +* 设置maven module artifactId等信息 + ![idea-module-maven-artifact](https://res.cloudinary.com/incoder/image/upload/v1547066737/blog/idea-module-maven-artifact.png) +* 设置maven module 名称及存储位置 + ![idea-module-maven-name](https://res.cloudinary.com/incoder/image/upload/v1547066735/blog/idea-module-maven-name.png) +#### springboot module project + +* 创建springboot module + ![idea-new-module-springboot](https://res.cloudinary.com/incoder/image/upload/v1547066076/blog/idea-new-module-springboot.png) +* 设置springboot module 信息 + ![idea-module-metadata](https://res.cloudinary.com/incoder/image/upload/v1547066075/blog/idea-module-metadata.png) +* 选择核心组件 + ![idea-module-springboot-core](https://res.cloudinary.com/incoder/image/upload/v1547066076/blog/idea-module-springboot-core.png) +* 设置springboot module 名称及存储位置 +![idea-module-springboot-name](https://res.cloudinary.com/incoder/image/upload/v1547066076/blog/idea-module-springboot-name.png) + +### Modify Config + +#### Modify parent pom diff --git a/source/_posts/idea-skill.md b/source/_posts/idea-skill.md new file mode 100644 index 000000000..71a3408e2 --- /dev/null +++ b/source/_posts/idea-skill.md @@ -0,0 +1,82 @@ +--- +title: 开发小技巧 +date: 2019-01-02 15:00:10 +categories: DevTool +tag: [Android Studio, JetBrains] +--- + +[Android Studio](https://zh.wikipedia.org/wiki/Android_Studio) 是Google基于JetBrains的 [IntelliJ IDEA](https://www.jetbrains.com/idea) 所定制开发的 Android 开发 IDE。因此这里的设置适用于 JetBrains 公司系列的开发工具,同样也适用于 Android Studio,这是一篇持续更新的文章,在平时的使用过程中一些习惯性的模板化的一些设置,可以减少我们一些重复性的操作,进而提高开发效率。 + + + +## 设置 + +快捷键: +* Windows:`Ctrl`+`Alt`+`S` +* macOS:`⌘`+`,` + +### 样式 + +#### 约束提示/空格及缩进 + +* 描述: + - 为了约束编写的代码过长而不换行,在代码编辑面板右侧右侧有个条竖线进行约束和警示,当然你可以关闭 + - 为了工整的显示代码的空格和换行是否正确,可以开启显示空格和缩进等样式 + +![idea-skill-line](https://res.cloudinary.com/incoder/image/upload/v1546416652/blog/idea-skill-line.png) + +#### 窗口打开全部展示 + +* 描述:为了在编辑器中展示全部打开的文件(不限制在同一行) + ![idea-open-windows-limit](https://res.cloudinary.com/incoder/image/upload/v1562844691/blog/idea-open-windows-limit.png) + + +### 颜色 + +#### 局部变量 + +* 描述:为了直观的区分出全局变量和局部变量,而不需要仔细阅读代码 +![idea-skill-local](https://res.cloudinary.com/incoder/image/upload/v1546417187/blog/idea-skill-local.png) + +#### 控制台日志 + +* 描述:为了直观在控制台上显示不同级别日志 +![idea-logcat-color](https://res.cloudinary.com/incoder/image/upload/v1562845142/blog/idea-logcat-color.png) + +颜色推荐: +* Assert:AA66CC +* Debug:33B5E5 +* Error:FF6B68 +* Info:99CC00 +* Verbose:BBBBBB +* Warning:FFBB33 + +### 其他 + +#### Toolbar添加设置按钮 + +* 描述:在不方便使用快捷键打开设置时,原本的操作是:File-->Settings Repository...,因此调整到状态栏上 +![idea-skill-settings](https://res.cloudinary.com/incoder/image/upload/v1546417198/blog/idea-skill-settings.png) + +#### 不区分大小写 + +* 描述:在编码过程中,通常一些智能提示需要根据输入的支付来提示,而大小写不同对应的提示也不完全一致,因此取消智能提示对大小写字符的要求 +![idea-skill-case](https://res.cloudinary.com/incoder/image/upload/v1546416873/blog/idea-skill-case.png) + +#### 自动导包 + +* 描述:在编码过程中,一些无用或者需要引入的包,可设置成自动的方式,当无法自动导入或移除无用包时,再手动的去选择处理 +![idea-skill-auto](https://res.cloudinary.com/incoder/image/upload/v1546416872/blog/idea-skill-auto.png) + +#### 字段默认前缀 +* 描述:为了让代码规范,我们会对变量前面设置默认前缀,那么 idea 也是支持 +![idea-field-prefix](https://res.cloudinary.com/incoder/image/upload/v1562845918/blog/idea-field-prefix.png) + +## 常用技巧 + +## 编码技巧 + +## 调试技巧 + +## 参考 +* [IntelliJ-IDEA-Tutorial](https://github.com/judasn/IntelliJ-IDEA-Tutorial) \ No newline at end of file diff --git a/source/_posts/idea-welfare.md b/source/_posts/idea-welfare.md new file mode 100644 index 000000000..d8815c75e --- /dev/null +++ b/source/_posts/idea-welfare.md @@ -0,0 +1,20 @@ +--- +title: JetBrains 系列激活 +date: 2020-02-27 13:12:10 +categories: DevTool +tag: [JetBrains] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1582782666/blog/idea.png) + + + +位于捷克的布拉格的[JetBrains](https://jetbrains.com)公司你可能不是很熟悉,但在开发的江湖中你一定听过他的名号,“**Java 开发最好用 IDE,没有之一**”,产品很好,但价格也不便宜。虽然有学生优惠,但对于非学生用户来说也是不小的开支。如果你想支持正版,官方还提供了一个开源项目渠道,你可以用你的开源项目进行申请,申请通过可以获得一年JetBrains全家桶的使用权限,这么好的福利当然不能错过。 + +我们先看看正常**个人订阅**价格图,IDEA:$149.00/1st year,全家桶产品:$249.00/1st year +![idea-price](https://res.cloudinary.com/incoder/image/upload/v1582782335/blog/idea-price.png) +如果你工资比较高,建议还是支持一下,废话不多说,我们一起来看看怎么通过开源项目申请使用产品 + +申请地址:https://www.jetbrains.com/shop/eform/opensource?product=ALL + +按照实际情况填写,大概一周左右,申请通过,JetBrains官方会发送邮件给你,你只需要按照邮件内的文件继续按照步骤进行操作,就可以获得 1 年的使用权限 \ No newline at end of file diff --git a/source/_posts/interview-2020.md b/source/_posts/interview-2020.md new file mode 100644 index 000000000..3140db2e1 --- /dev/null +++ b/source/_posts/interview-2020.md @@ -0,0 +1,104 @@ +--- +title: 2020 年秋季面试经历 +date: 2020-11-15 08:10:17 +categories: Summary +tag: [Interview, Summary] +password: 1024 +abstract: 这是一篇加密博文,请输入密码后查看 +message: 这里需要密码才能访问 +wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试…… +--- + +金三银四,金九银十,铁打的招聘,流水的面试。作为打工人,谁不想拥有满意的薪资呢,不然折腾来折腾去的徒增烦恼,记录本次面试的经历,方便进行复盘和针对自己的薄弱领域进行强化。 + + + +## 万益智汇 + +境外支付+电商 + +## 得盛劳务 + +## 白鸟科技 + +offer + +## 米链科技 + +## 米卡迪 + +## 促佳贸易 + +笔试 + +## 华通云数据 + +电话面试 + +## 厚米农业 + +offer + +## 希和云智 + +## 杭州卓拓 + +offer + +## 觅览 + +offer + +## 数慧享 + +电话+现场 + +## 红岭通 + +offer + +## 安能物流 + +电话+现场 + +## 壹体育 + +JVM 的内存模型 + +Netty 的拆包和粘包 +Netty 的协议自定义 +Netty 读脏怎么解决 +Netty 常用的网络协议,并讲一讲他的过程 + +Mybatis 是怎么 + +HashMap 的 key为 null 的时候,是怎么去 hash 的 + +Map 的循环方法 + +反射的优缺点 + +事务的特性 + +数据库引擎 + +## 总结 + +面试本质是一次交流学习的过程 + +## 参考 + +### 简历相关 + +1. [如何提炼项目经验中的亮点?](https://faxian.lagou.com/discover/249870e113784b0683c3f57c1fd147ca.html) +2. [涨薪不到20%的跳槽都是白跳?](https://faxian.lagou.com/discover/60fa66cf74314990b4d4f8502cb8fd7e.html) +3. [通过大厂筛选的程序员简历都是怎样的?](https://faxian.lagou.com/discover/722e3072efb5471ea51ebfc5101efbbb.html) +4. [10年的hr经验分享:最全面试前准备干货!](https://faxian.lagou.com/discover/3752aff7285e40be820b1183a0017156.html) +5. [简历的8个减分项](https://faxian.lagou.com/discover/82e50031d02340e2bbb0cd9e0451646c.html) +6. [程序员面试10大潜规则](https://faxian.lagou.com/discover/263ff1f9bf80466fb45dbd17c78c6250.html) +7. [敲黑板,Java求职必备流程图来啦!](https://faxian.lagou.com/discover/e85beb460ff44f99ba82e4137d10bec5.html) +8. [面试陷阱题:你的缺点是什么?](https://faxian.lagou.com/discover/50b893e17a9d4ee6b1c8aee46abfa58c.html) + +### 面试资料 + +1. [Interview](https://github.com/shishan100/Java-Interview-Advanced) diff --git a/source/_posts/linux-build.md b/source/_posts/linux-build.md new file mode 100644 index 000000000..62d2fda98 --- /dev/null +++ b/source/_posts/linux-build.md @@ -0,0 +1,496 @@ +--- +title: Linux 常用应用安装 +date: 2018-05-15 00:32:10 +categories: Linux +tag: Build +--- + +作为 Android 开发者,目标主要是在客户端,平时也就是和服务端对接数据接口,很少直接干到服务端的 Linux 机器,随着这波推动团队技术平台基础开发工具模块的完善,拿到了一台 Linux 机器,重新构建移动端的测试服务器。 + +该机器主要功能: +1. 提供移动端服务 Api 接口 +2. 提供移动端通讯录管理授权服务 +3. 提供企业微信通讯录同步服务 +4. 管理移动端服务器 Api 接口文档 + + + +也是第一次正式的从头开始安装所需软件及应用部署,虽然这些工作可以完全找运维去处理,难得这样的机会从头开始去熟悉 Linux。 + +**安卓,是一个基于Linux内核的开放源代码移动操作系统**,因此多了解 Linux 是一件双赢的事情,基于当前机器需要提供的服务,安装部署需要的软件应用 + +
废话不多说,上来就是干
+ +和 Windows 一样不同的系统,安装的软件也是有区别的,而且 Linux 的系统众多,因此需要先查看系统的版本及相关信息,然后再下载对应系统版本的应用进行安装 + +## 查看 Linux 发行版的名称及其版本号 + +### 查看内核 + +1. `cat /proc/version` + ``` + [dc2-user@10-255-0-191 ~]$ cat /proc/version + Linux version 3.10.0-957.27.2.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Mon Jul 29 17:46:05 UTC 2019 + ``` +2. `uname -a` + ``` + [dc2-user@10-255-0-191 ~]$ uname -a + Linux 10-255-0-191 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux + ``` + +### 查看 Linux 系统版本 + +1. `lsb_release -a`:列出所有版本信息 + ``` + [dc2-user@10-255-0-191 ~]$ lsb_release -a + LSB Version: :core-4.1-amd64:core-4.1-noarch + Distributor ID: CentOS + Description: CentOS Linux release 7.6.1810 (Core) + Release: 7.6.1810 + Codename: Core + ``` +2. `cat /etc/redhat-release`:只适合 Redhat 系的 Linux + ``` + [dc2-user@10-255-0-191 ~]$ cat /etc/redhat-release + CentOS Linux release 7.6.1810 (Core) + ``` +3. `cat /etc/issue`:此命令也适用于所有的 Linux 发行版 + ``` + [root@localhost ~]# cat /etc/issue + CentOS release 6.7 (Final) + Kernel \r on an \m + ``` +## Java + +[官方下载地址](http://www.oracle.com/technetwork/java/javase/downloads/index.html),选择需要的版本下载安装包 +>官方提供了`.rpm`,`.gz`两种格式安装包 + +```bash +# 1. 下载安装包 +# 拷贝安装包到需要安装的服务器 +# 2. 解压并安装 +# .rpm 格式安装(jdk-xxx.rpm更换成对应的文件名) +sudo rpm -ivh jdk-xxx.rpm +# .gz 格式安装(解压到指定目录,常存放 /usr/java/ 路径) +tar zxvf jdk-xxx.tar.gz -C /usr/java/ +# 3. 设置环境变量 +vim /etc/profile +# 指定 JDK 的配置信息(修改这里路径,指向 jdk 安装路径) +JAVA_HOME=/usr/java/jdk1.8.0_172 +PATH=$JAVA_HOME/bin:$PATH +CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar +export JAVA_HOME PATH CLASSPATH +# 4. 编译配置文件,使修改生效 +source /etc/profile +# 5. 验证 jdk 是否安装成功 +java –version +``` + +## Tomcat + +[官方下载地址](http://tomcat.apache.org),选择需要的版本下载安装包 +>官方提供了`.zip`,`.gz`两种格式安装包,Linux服务器下载`Core`类即可 + +```bash +# 1. 下载安装文件 +wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-9/v9.0.8/bin/apache-tomcat-9.0.8.tar.gz +# 2. 解压安装文件(解压到指定目录,常存放 /usr/tomcat/ 路径) +tar -zxvf apache-tomcat-9.0.8.tar.gz -C /usr/tomcat/ +# 3. 启动 tomcat +cd /usr/local/tomcat/bin +./startup.sh +# 4. 关闭 tomcat +./shutdown.sh +``` + +### 配置Web管理账号 + +* 修改文件 conf/tomcat-users.xml,在元素中添加帐号密码,需要指定角色 + ```bash + vim /usr/local/tomcat/conf/tomcat-users.xml + # + # + # + ``` + +### 配置端口 + +* 可以修改 conf 目录下的文件 server.xml,修改 Connector 元素(Tomcat 的默认端口是 8080),需要重新启动 Tomcat 服务生效 + ```bash + vim /usr/local/tomcat/conf/server.xml + # + ``` + +### 应用部署 + +* 放置需部署包到容器中 `webapps` 路径 + ```bash + cd /usr/local/tomcat/webapps + ``` +* 启动服务 + ```bash + cd /usr/local/tomcat/bin + ./startup.sh + ``` +## Maven + +[官方网站](http://tomcat.apache.org/),选择需要的版本下载 + +* 官方 Maven: https://maven.apache.org/ +* Maven 下载地址: https://maven.apache.org/download.cgi +* Maven 历史版本: https://archive.apache.org/dist/maven/maven-3/ + +### 安装 + +```bash +# 下载文件 Maven 文件 +wget https://mirrors.bfsu.edu.cn/apache/maven/maven-3/3.8.1/binaries/apache-maven-3.8.1-bin.tar.gz +# 解压 Maven 文件 +tar -xvf apache-maven-3.8.1-bin.tar.gz +# 移动 Maven 文件到 /usr/local/ 路径 +sudo mv -f apache-maven-3.8.1 /usr/local/ +``` + +### 配置 + +```bash +# 编辑环境配置 +vim /etc/profile +# 添加如下环境变量,文件末尾添加如下代码 +export MAVEN_HOME=/usr/local/apache-maven-3.8.1 +export PATH=${PATH}:${MAVEN_HOME}/bin +# 使环境变量生效 +source /etc/profile +``` + +### 验证 + +```bash +mvn -v +``` + +如果需要更改 Maven 的镜像源,可参考 [专治各种网络不服](https://incoder.org/2020/02/27/fuck-gfw/#Maven) 文章 + +## Apache + +一般系统中已经包含 Apache 应用 +[官方下载地址](http://httpd.apache.org/download.cgi),选择需要的版本下载安装包 +>官方提供了`.bz2`,`.gz`两种格式安装包 + +### 安装 + +查看系统中是否已包含 httpd 应用 +```bash +rpm -qa | grep httpd +# 或 +yum list | grep httpd +``` + +* 方式一 + ```bash + # 1. 下载需要的版本文件 + wget http://apache.claz.org//httpd/httpd-2.4.33.tar.gz + # 2. 解压安装文件(解压到指定目录,常存放 /usr/local/httpd/ 路径) + tar -zxvf httpd-2.4.33.tar.gz -C /usr/local/httpd/ + ``` + +* 方式二(推荐) + ```bash + # 1.下载安装 httpd + yum install httpd + ``` + +### 卸载 + +```bash +yum erase httpd.x86_64 +# 或 +rpm -e httpd.x86_64 +``` + +### 常用命令 + +```bash +# 查看服务运行状态 +systemctl status httpd.service +# 启动 apache 服务 +systemctl start httpd.service +# 停止 apache 服务 +systemctl stop httpd.service +``` + +RPM默认安装路径: + +| 路径 | 说明 | +| ---------- | --- | +| /etc | 一些设置文件放置的目录如 /etc/crontab | +| /usr/bin | 一些可执行文件 | +| /usr/lib | 一些程序使用的动态函数库 | +| /usr/share/doc | 一些基本的软件使用手册与帮助文档 | +| /usr/share/man | 一些 man page 文件 | + +## Nginx + +[官方下载地址](http://nginx.org/download),选择需要的版本下载安装包(最新安装版本 1.14.0) +>官方提供了`.zip`,`.gz`两种格式安装包 + +### 安装 + +* 方式一 + ```bash + # 1. 下载安装文件 + wget http://nginx.org/download/nginx-1.14.0.tar.gz + # 2. 解压安装文件(解压到指定目录,常存放 /usr/local/ 路径) + tar -zxvf nginx-1.14.0.tar.gz -C /usr/local/ + # 3. 编译安装依赖库 + cd /usr/local/nginx/ + ./configure + ``` + +* 方式二 + ```bash + # 默认安装路径 /etc/nginx/ + yum install nginx + ``` + +### 常用命令 + +* 加压文件安装常用命令 + ```bash + # 停止 ngix + /usr/local/nginx/sbin/nginx -s quit + # 重新载入 nginx(当配置信息发生修改时) + /usr/local/nginx/sbin/nginx -s reload + # 查看版本 + /usr/local/nginx/sbin/nginx -v + # 查看 nginx 的配置文件的目录 + /usr/local/nginx/sbin/nginx -t + # 查看帮助信息 + /usr/local/nginx/sbin/nginx -h + ``` +* yum安装常用命令 + ```bash + # 启动 + systemctl start nginx + # 停止 + systemctl stop nginx + # 重启 + systemctl restart nginx + # 查看运行状态 + systemctl status nginx + # 开机启动 + systemctl enable nginx + ``` + +## Node + +[官方下载地址](https://nodejs.org),选择需要的版本下载安装包 +>官方提供了`.gz`,`.7z`,`zip`等多种格式安装包 + +### 安装 + +```bash +# 1. 下载安装文件 +wget https://nodejs.org/download/chakracore-release/v8.6.0/node-v8.6.0-linux-x64.tar.gz +# 2. 解压安装文件(解压到当前目录) +tar -zxf node-v8.6.0-linux-x64.tar.gz +# 3. 建立软链接,实现全局访问 +ln -s /root/node-v8.6.0-linux-x64/bin/node /usr/local/bin/node +ln -s /root/node-v8.6.0-linux-x64/bin/npm /usr/local/bin/npm +``` + +## Redis + +[官方下载地址](https://redis.io/download),选择需要的版本下载安装包 +>官方提供了`.gz`格式安装包 + +### 安装 + +```bash +# 1. 下载安装文件 +wget wget http://download.redis.io/releases/redis-4.0.10.tar.gz +# 2. 解压安装文件(解压到当前目录) +tar xzf redis-4.0.10.tar.gz +# 3. 编译安装 +cd redis-4.0.10 +# 编译 +make +# 4. 启动服务 +src/redis-server +``` + +## 配置 + +```bash +# 修改 redis.conf 文件中 daemonize 属性 为 yes +vim /you_install_path/redis.conf +``` +> 其他配置根据自身需要调整修改 + +## 其他命令 + +1. 关闭服务 + ```bash + redis-cli -h 127.0.0.1 -p 6379 shutdown + ``` +2. 非安全模式启动 + ```bash + # 后台以非安全模式启动 + nohup /usr/local/bin/redis-server --protected-mode no & + ``` + +## 常用命令 + +### 文件查找 + +#### find + +find 命令是根据文件的属性进行查找,如文件名,文件大小,所有者,所属组,是否为空,访问时间,修改时间等。 +* 基本格式: +find path expression +* 示例: + * 在根目录下查找文件 httpd.conf,表示在整个硬盘查找 + find / -name httpd.conf + * 表示当前目录下查找文件名开头是字符串 srm 的文件 + find . -name 'srm*' + * 查找在系统中最后 10 分钟访问的文件(access time) + find / -amin -10 + * 查找在系统中属于 fred 这个用户的文件 + find / -user fred + * 查找出小于 1000KB 的文件 + find / -size -1000k + +#### grep + +grep 是根据文件的内容进行查找,会对文件的每一行按照给定的模式(patter)进行匹配查找。 +* 基本格式: +find expression +* 主要参数: + -c:只输出匹配行的计数。 + -i:不区分大小写 + -h:查询多文件时不显示文件名。 + -l:查询多文件时只输出包含匹配字符的文件名。 + -n:显示匹配行及行号。 + -s:不显示不存在或无匹配文本的错误信息。 + -v:显示不包含匹配文本的所有行。 +* 示例: + * 显示所有包含每行字符串至少有 5 个连续小写字符的字符串的行 + grep ‘[a-z]\{5\}’ aa + * 显示所有以 d 开头的文件中包含 test 的行 + grep 'test' d* + +### 进程相关 + +* 查看指定服务进程 + ```bash + # 查看 httpd 服务进程 + ps -ef | grep httpd + # UID PID PPID C STIME TTY TIME CMD + # root 7192 7103 0 19:59 pts/3 00:00:00 grep --color=auto httpd + ``` + + {% note info %} + * UID:用户 ID + * PID:进程 ID + * PPID:父进程 ID + * C:CPU 用于计算执行优先级的因子。数值越大,表明进程是 CPU 密集型运算,执行优先级会降低;数值越小,表明进程是 I/O 密集型运算,执行优先级会提高 + * STIME:进程启动的时间 + * TTY:完整的终端名称 + * TIME:CPU 时间 + * CMD:完整的启动进程所用的命令和参数 + {% endnote %} + +* 杀死指定进程 + ```bash + kill -9 pid(逐个都删除) + ``` +* 查看指定端口 + ```bash + # 检测 6379 端口是否在监听 + netstat -lntp | grep 6379 + ``` + +### 文件复制 + +#### 语法 + +``` +scp(选项)(参数) +``` + +#### 选项 + +``` +-1:使用 ssh 协议版本 1; +-2:使用 ssh 协议版本 2; +-4:使用 ipv4; +-6:使用 ipv6; +-B:以批处理模式运行; +-C:使用压缩; +-F:指定ssh配置文件; +-i:identity_file 从指定文件中读取传输时使用的密钥文件(例如亚马逊云 pem),此参数直接传递给ssh; +-l:指定宽带限制; +-o:指定使用的 ssh 选项; +-P:指定远程主机的端口号; +-p:保留文件的最后修改时间,最后访问时间和权限模式; +-q:不显示复制进度; +-r:以递归方式复制。 +``` + +#### 参数 + +* 源文件:指定要复制的源文件 +* 目标文件:目标文件。格式为 user@host:filename(文件名为目标文件的名称) + +#### 示例 + +* 上传本地文件到远程机器指定目录 + ``` + scp /opt/soft/nginx-0.5.38.tar.gz root@10.10.10.10:/opt/soft/scptest + # 指定端口 2222 + scp -rp -P 2222 /opt/soft/nginx-0.5.38.tar.gz root@10.10.10.10:/opt/soft/scptest + ``` + +* 上传本地目录到远程机器指定目录 + ``` + scp -r /opt/soft/mongodb root@10.10.10.10:/opt/soft/scptest + ``` + +* 从远程机器复制文件到本地 + ``` + scp root@10.10.10.10:/opt/soft/nginx-0.5.38.tar.gz /opt/soft/ + ``` + +* 从远程机器复制文件(含目录)到本地 + ``` + scp -r root@10.10.10.10:/opt/soft/mongodb /opt/soft/ + ``` + +### 文件删除 + +#### 语法 + +```bash +rm [选项] 文件或目录 +``` + +#### 选项 + +``` +-f:强行删除,忽略不存在的文件,不提示确认。(f 为 force 的意思) +-i:进行交互式删除,即删除时会提示确认。(i 为 interactive 的意思) +-r:将参数中列出的全部目录和子目录进行递归删除。(r 为 recursive 的意思) +-v:详细显示删除操作进行的步骤。(v 为 verbose 的意思) +``` + +#### 示例 + +* 删除一个文件 + ```bash + rm file + ``` + +* 删除一个目录 + ```bash + rm file/ + ``` \ No newline at end of file diff --git a/source/_posts/linux-mysql.md b/source/_posts/linux-mysql.md new file mode 100644 index 000000000..db3130892 --- /dev/null +++ b/source/_posts/linux-mysql.md @@ -0,0 +1,314 @@ +--- +title: Linux 之 MySQL +date: 2018-07-23 22:30:10 +categories: Linux +tag: MySQL +--- + +之前粗略的接触了Linux的基础使用和安装,这次准备在自购的服务器上跑些应用,纯属娱乐,废话不说,上来就先仍数据库。 +数据库常用的`Oracle`,`MySQL`,`SQL Server`,`MongoDB`等,排名不分先后,自己平时接触最多的也就是`MySQL`,`MongoDB`,好`MySQL`先来一份。 + +## 介绍 + +MySQL是一个开源数据库管理系统,通常作为流行的LEMP(Linux,Nginx,MySQL / MariaDB,PHP / Python / Perl)堆栈的一部分安装。它使用关系数据库和SQL(结构化查询语言)来管理其数据。 + +[CentOS 7](https://www.centos.org)更喜欢[MariaDB](https://mariadb.org),它是由原始`MySQL`开发人员管理的`MySQL`分支,旨在替代MySQL。如果你在CentOS 7上运行 `yum install mysql`,那么安装的是MariaDB,而不是MySQL + + + +对于 MySQL 也是有好几个类别 +1. MySQL Community Server:社区版本,开源免费,但不提供官方技术支持 +2. MySQL Enterprise Edition:企业版本,需付费,可以试用 30 天 +3. MySQL Cluster 集群版:开源免费。可将几个 MySQL Server 封装成一个 Server +4. MySQL Cluster CGE:高级集群版,需付费 +5. MySQL Workbench:一款专为 MySQL 设计的 ER/数据库建模工具,分为两个版本 + * MySQL Workbench OSS:社区版 + * MySQL WorkbenchSE:商用版 + +先检查服务器是否已经安装了mariadb +```bash +# 检查是否安装了 mariadb 客户端 +rpm -qa | grep mariadb +# 检查是否安装了 mariadb 服务端 +rpm -qa | grep mariadb-server +``` +执行结果如下图,如果没有任何提示,则表示没有安装 +![](https://res.cloudinary.com/incoder/image/upload/v1606229882/blog/mariadb-check.png) + +执行检查命令,发现有 mariadb 服务,则需要先卸载 `mariadb` 相关服务 +```bash +# mariadb 相关卸载 +rpm -qa |grep mariadb |xargs yum remove -y +``` + +![](https://res.cloudinary.com/incoder/image/upload/v1606235520/blog/mariadb-uninstall.png) + +## 清单 + +* OS: CentOS 7 +* DataBase:MySQL 8.0.11 + +>`uname -a`查看你 Linux 系统的信息,按照系统版本选择对应的应用 + +``` +[dc2-user@10-255-0-191 ~]$ uname -a +Linux 10-255-0-191 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux +# 这里,el7.x86_64 分别表示,el7:CentOS 7,x86_64:64 位系统 +``` + +## 安装 + +>官方下载 MySQL Community Server 地址:https://dev.mysql.com/downloads/mysql/ +>清华镜像 MySQL 地址:https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/ + +![](https://res.cloudinary.com/incoder/image/upload/v1606147634/blog/mysql-download.png) + +### 在线安装(推荐方式) + +适用于当前安装服务器可以正常互联网访问,如上图,选择方式进入[MySQL Yum Repository](https://dev.mysql.com/downloads/repo/yum),选择对应系统的版本,比如 `mysql80-community-release-el7-3.noarch.rpm` + +![](https://res.cloudinary.com/incoder/image/upload/v1606227057/blog/mysql-line-rpm.png) + +根据自己设备网络情况选择使用的下载地址 +* 官方地址:https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm +* 清华镜像:https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql-8.0-community-el7-x86_64/mysql80-community-release-el7-3.noarch.rpm + +```bash +# 1. 获取官方yum源安装包 mysql80-community-release-el7-3.noarch.rpm 是根据官网提供的版本信息 +wget https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm +# 2. 安装rpm包 +rpm -ivh mysql80-community-release-el7-3.noarch.rpm +# 3. 安装mysql-server +yum install -y mysql-server +# 如果你需要设置不区分大小写,必须在第一次启动 mysqld 服务前设置,lower_case_table_names=1 +# vim /etc/my.cnf +# 4. 启动mysqld服务 +systemctl start mysqld +# 5. 查看是否成功启动 +ps aux|grep mysqld +# 6. 设置mysqld服务开机自启动 +systemctl enable mysqld +``` + +>设置不区分大小写参数,文件地址 `/etc/my.cnf` + +### 离线安装 + +适用于当前安装服务器无法互联网访问,如安装截图所示,选择 [MySQL Community Server](https://dev.mysql.com/downloads/mysql/) 类别,然后选择对应系统及版本进行下载,总共需要如下所示相关的应用安装包 + +按照此顺序进行安装 + +```shell +rpm -ivh mysql-community-common-8.0.22-1.el7.x86_64.rpm +rpm -ivh mysql-community-client-plugins-8.0.22-1.el7.x86_64.rpm +rpm -ivh mysql-community-libs-8.0.22-1.el7.x86_64.rpm +rpm -ivh mysql-community-libs-compat-8.0.22-1.el7.x86_64.rpm +rpm -ivh mysql-community-embedded-compat-8.0.22-1.el7.x86_64.rpm +rpm -ivh mysql-community-devel-8.0.22-1.el7.x86_64.rpm +rpm -ivh mysql-community-client-8.0.22-1.el7.x86_64.rpm +rpm -ivh mysql-community-server-8.0.22-1.el7.x86_64.rpm +``` +安装 `mysql-community-devel-8.0.22-1.el7.x86_64.rpm` 时出错,需要先执行 `yum install openssl-devel` 命令进行安装 openssl-devel,如下图 +![](https://res.cloudinary.com/incoder/image/upload/v1606415382/blog/mysql-community-devel.png) + +安装完上面的包,进行启动 MySQL 服务,并进行相应的配置 +```bash +# 如果你需要设置不区分大小写,必须在第一次启动 mysqld 服务前设置,lower_case_table_names=1 +# vim /etc/my.cnf +# 启动mysqld服务 +systemctl start mysqld +# 查看是否成功启动 +ps aux|grep mysqld +# 设置mysqld服务开机自启动 +systemctl enable mysqld +``` + +整个安装设置过程如下图 +![](https://res.cloudinary.com/incoder/image/upload/v1607229288/blog/mysql-offline-install.png) + +## 配置 + +由于MySQL从5.7开始不允许在首次安装后,使用空密码进行登录,系统会随机生成一个密码以供管理员首次登录使用,这个密码记录在`/var/log/mysqld.log`文件中 + +```bash +# 1. 查看系统提供密码 +cat /var/log/mysqld.log|grep 'A temporary password' +# 2. 使用获取到的密码登录MySQL +mysql -u root -p +# 3. 切换数据库 +use mysql; +# 4. 修改root密码 your_password 替换成你自己的密码就可以了,这个密码是强密码,要求密码包含大小写字母、数字及标点符号,长度大于6 +alter user 'root'@'localhost' identified by 'your_password'; +# 5. 刷新修改 +flush privileges; +``` + +## 默认信息 + +### 配置信息地址 + +默认安装配置信息:/etc/my.cnf + +### MySQL 安装路径 + +```bash +# 查看 MySQL 安装的位置 +which mysqld +``` + +### 其他配置信息 + +![](https://res.cloudinary.com/incoder/image/upload/v1607229288/blog/mysql-cnf.png) + +## 连接 + +自己平时习惯使用 [Navicat](https://www.navicat.com.cn) 进行数据库操作,因此这里进行配置链接已在云端刚刚安装的MySQL服务 +![linux-mysql](https://res.cloudinary.com/incoder/image/upload/v1532362215/blog/linux-mysql.png) + +### ERROR 2003 + +通过本地的工具无法连接到服务器上的 MySQL 服务,错误提示:`ERROR 2003 (HY000): Can't connect to MySQL server on '116.85.58.6' (60)` + +进行原因排查,分别通过 `ping`,`telnet` 命令来检查网络连接情况,发现 IP 是通的,端口不同,那么去看服务器是不是开启了防火墙,如果开启了防火墙,需要将 MySQL 服务的端口排除 + +{% note warning %} +对于开启了防火墙的,可以将防火墙关闭,或者设置端口白名单 +{% endnote %} +```bash +# 查看防火墙状态,结果显示为running或not running +## 如果显示,-bash: firewall-cmd: command not found,则表示当前服务器没有安装防火墙 +firewall-cmd --state +# 关闭防火墙firewall +systemctl stop firewalld.service +systemctl disable firewalld.service +# 关闭防火墙firewall后开启 +systemctl start firewalld.service +# 开启端口 +## zone -- 作用域 +## add-port=80/tcp -- 添加端口,格式为:端口/通讯协议 +## permanent -- 永久生效,没有此参数重启后失效 +firewall-cmd --zone=public --add-port=3306/tcp --permanent +## 开启3306端口后,workbench或naivcat 就能连接到MySQL数据库了 +``` + +![](https://res.cloudinary.com/incoder/image/upload/v1606417200/blog/mysql-link.png) + +SSH 连接 MySQL 所在的服务器,查看防火墙情况,但发现压根没有防火墙,那么此时,要去看看你的云服务器的安全组设置规则,将 3306 暴露出来,查看云服务器安全组设置 +![](https://res.cloudinary.com/incoder/image/upload/v1606418056/blog/didi-rule.png) + +在已有安全组中添加安全规则或者新增安全组在新的安全组中添加安全规则 +![](https://res.cloudinary.com/incoder/image/upload/v1606418056/blog/didi-add-rule.png) + +### ERROR 1130 + +按照上图图的配置信息链接MySQL,发现错误提示:`ERROR 1130: Host 'xxx.xxx.xxx.xxx' is not allowed to connect to this MySQL server` + +#### 原因 + +不允许从远程登陆MySQL服务,只能在localhost + +#### 解决方法 + +```bash +# 切换数据库 +use mysql; +# 修改user 指定的host 为 % +update user set host = '%' where user = 'root'; +# 查看 user 表中的信息 +select host, user from user; +# 成功修改s ++-----------+------------------+ +| host | user | ++-----------+------------------+ +| % | root | +| localhost | mysql.infoschema | +| localhost | mysql.session | +| localhost | mysql.sys | ++-----------+------------------+ +4 rows in set (0.00 sec) +``` + +### ERROR 2059 + +继续重试链接,错误提示:`ERROR 2059: Authentication plugin 'caching_sha2_password' cannot be loaded:The specified module could not be found.` + +#### 原因 + +MySQL 8不支持动态修改密码验证方式 + +#### 解决方法 + +##### 方法一 + +修改plugin默认的 `caching_sha2_password` 为 `mysql_native_password` + +```bash +# 停止mysql +systemctl stop mysqld.service +# my.cnf文件中默认有下面的语句,删除前面的#号即可,没有的话就把它添加到my.cnf中 ,默认路径`/etc/my.cnf` +default-authentication-plugin=mysql_native_password +# 切换数据库 +use mysql +# 给指定用户设置密码,这里`%`是因为之前已经将远程没有特殊指定,用%代替了localhost +ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'your_password'; +``` + +##### 方法二 + +更新你的数据库连接工具,Navicat version12+,Workbench version8+ + +### BDB1507 + +BDB1507 Thread died in Berkeley DB library,由于其他操作原因造成数据库被破坏问题,可以通过如下命令进行数据库修复 + +```bash +cd /var/lib/rpm/ +for i in `ls | grep 'db.'`;do mv $i $i.bak;done +rpm --rebuilddb +yum clean all +``` + +![](https://res.cloudinary.com/incoder/image/upload/v1607750118/blog/rpm-rebuild.png) + +## 卸载 + +### 应用卸载 + +```shell +# 快速删除 +yum remove mysql mysql-server mysql-libs mysql-server +# 查找残留文件(例如:mysql-community-server-8.0.22-1.el7.x86_64) +rpm -qa | grep -i mysql +# 将查询出来的应用删除 +yum remove mysql-community-server-8.0.22-1.el7.x86_64 +# 查找残余目录 +## /etc/selinux/targeted/active/modules/100/mysql +## /etc/selinux/targeted/tmp/modules/100/mysql +## /var/lib/mysql +## /var/lib/mysql/mysql +## /usr/share/mysql +find / -name mysql +# 删除残余目录 +rm -rf /etc/selinux/targeted/active/modules/100/mysql +rm -rf /etc/selinux/targeted/tmp/modules/100/mysql +rm -rf /var/lib/mysql +rm -rf /var/lib/mysql/mysql +rm -rf /usr/share/mysql +# 再次检查残余目录,确保没有残余 +find / -name mysql +``` + +### 开机自启(可选) + +当你安装时设置了开机启动,当不需要再开机启动时,那么再卸载完数据库后,记得在开启启动的列表中删除数据库 + +```bash +chkconfig --list | grep -i mysql +chkconfig --del mysqld +``` + +## 附录 + +* [How To Install MySQL on CentOS 7](https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-centos-7) +* [ERROR 1130](https://blog.csdn.net/nyist327/article/details/45074559) +* [ERROR 2059](https://blog.csdn.net/airt_xiang/article/details/80261674) \ No newline at end of file diff --git a/source/_posts/lombok.md b/source/_posts/lombok.md new file mode 100644 index 000000000..e1550b5bc --- /dev/null +++ b/source/_posts/lombok.md @@ -0,0 +1,335 @@ +--- +title: Lombok +date: 2019-08-21 22:00:46 +categories: Util +tag: [Util] +--- + +在实际开发过程中,不管是服务端(Java),还是客户端(Android)都需要创建对应的实例bean对象,用来实例化对象,在对需要实例化的对象中,通常需要写 `set`,`get` 方法,字段少的时候,还能忍受,尤其当客户端字段多的时候,而且字段类型或者字段名称来回改动时,稍不注意,就很大机率修改不全面,就会造成一些隐藏bug,有没有不需要手动取写(快捷键生成)这些方法,当然有,本片文章,我们就来学习 [Lombok](https://projectlombok.org) + +Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. +Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more(Project Lombok是一个java库,可以自动插入编辑器并构建工具,为您的java增添色彩。 永远不要再写另一个getter或equals方法,使用一个注释,你的类有一个功能齐全的构建器,自动化你的日志记录变量等等),这是官方对Lombok的介绍,简单来讲就是通过注解的方式,代替一些重复性的代码,让代码更加简洁 + + + +## 安装集成 + +* 编辑器(IDEA or Android Studio)安装 Lombok 插件,`File` -> `Settings...` -> `Plugins` -> `搜索 Lombok 并安装` +* 开启编辑器 Annotation Processors + ![](https://res.cloudinary.com/incoder/image/upload/v1566701581/blog/idea-annotation-processors.png) +* 项目集成Lombok依赖,项目按照不同的包管理, 按照对应方式添加包依赖 + ```xml + + + repositories { + mavenCentral() + } + + dependencies { + implementationOnly 'org.projectlombok:lombok:1.18.8' + annotationProcessor 'org.projectlombok:lombok:1.18.8' + } + + + + + + org.projectlombok + lombok + 1.18.8 + provided + + + ``` + +## 稳定注解 + +Lombok提供稳定的注解,可以直接在生成环境使用,`org.projectlombok.lombok`包`lombok`路径下 + +### val + +**val** 作为任何局部变量声明类型(包含 for语句声明),并且该类型会推断初始化表达式的类型,同时也是`final`的。 + +* 0.10版本加入该功能 +* 使用场景:局部变量声明 + +```java +// x 将被推断为 double 类型,并且是final +val x = 10.0; +// y 将被推断为 ArrayList 类型,并且是final +val y = new ArrayList(); +// z 将被转换为 final int z = 10; +val z = 10; +``` + +> 官方示例[val](https://projectlombok.org/features/val) + +### var + +**var** 作为任何局部变量声明类型(包含 for语句声明),并且该类型会推断初始化表达式的类型。 + +* 1.16.20版本加入该功能 +* 使用场景:局部变量声明 + +```java +// x 将被推断为double,转换为 double x = 10.0d; +var x = 10.0; +``` + +> 官方示例[var](https://projectlombok.org/features/var) + +### @NonNull + +注解在 **属性** 上,会自动产生一个关于此参数的非空检查,如果参数为空,则抛出一个空指针异常,也会有一个默认的无参构造方法 + +* 0.11.10版本加入该功能 +* 支持字段,方法,参数,局部变量,枚举 + +> 官方示例[@NonNull](https://projectlombok.org/features/NonNull) + +### @Cleanup + +自动资源管理,安全地调用close()方法,确保通过调用 close() 方法清除你注释的变量声明,无论发生声明情况 + +* 支持局部变量 + +```java +public void copyFile(String in, String out) throws IOException { + @Cleanup FileInputStream inStream = new FileInputStream(in); + @Cleanup FileOutputStream outStream = new FileOutputStream(out); + byte[] b = new byte[65536]; + while (true) { + int r = inStream.read(b); + if (r == -1) break; + outStream.write(b, 0, r); + } +} +``` + +> 官方示例[@Cleanup](https://projectlombok.org/features/Cleanup) + +### @Getter/@Setter + +* 注解在 **属性** 上;为单个属性提供 set/get 方法; +* 注解在 **类** 上,为该类所有的属性提供 set/get 方法, 都提供默认构造方法 +* 等级可自己更改 PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE,默认Public + +```java +// 可手动设置字段的set方法为你需要的修饰类型 +@Setter(AccessLevel.PROTECTED) +private String name; +``` + +> 官方示例[@Getter/@Setter](https://projectlombok.org/features/GetterSetter) + +### @ToString + +任何 **类** 定义都可以用 @ToString 注释,让lombok生成 toString() 方法的实现。默认情况下,它会按顺序打印您的类名以及每个字段,并以逗号分隔 + +* includeFieldNames,默认true:打印时包括每个字段的名称 +* callSuper,默认false:在输出中包含超类的实现的结果 +* doNotUseGetters,默认false:通常情况下,如果有可用的getter,那么就会调用它们。要禁止此操作并让生成的代码直接使用这些字段,请将其设置为true +* onlyExplicitlyIncluded,默认false:仅包含用明确标记的字段和方法。通常,默认情况下包含所有(非静态)字段 + +```java +// 排除该字段一同生成在 toString()方法中 +@ToString.Exclude +private int id; +// 配置在toString中呈现此成员的行为;如果在方法上,请在输出中包含方法的返回值 +// rank(默认为0):首先打印更高的等级。相同级别的成员按照它们在源文件中出现的顺序打印 +// name(默认为""):默认为带注释的成员的 字段/方法 名称。如果名称等于默认包含字段的名称,则此成员将取代它 +@ToString.Include(rank = 0, name = "") +private int id; +``` + +> 官方示例[@ToString](https://projectlombok.org/features/ToString) + +### @EqualsAndHashCode + +注解在 **类** 上, 可以生成 equals、canEqual、hashCode 方法,与 @Data 相比,少了 toString() 方法,部分属性和 @ToString 注解相同 + +> 官方示例[@EqualsAndHashCode](https://projectlombok.org/features/EqualsAndHashCode) + +### Constructor + +按照定制生成构造函数 + +* @NoArgsConstructor:生成不带参数的构造函数 +* @RequiredArgsConstructor:生成带有必需参数的构造函数。参数是final字段和字段是具有约束的,例如@NonNull +* @AllArgsConstructor:生成一个全参构造函数 + +> 官方示例[Constructor](https://projectlombok.org/features/constructor) + +### @Data + +生成所有字段的 getter,一个有用的 toString 方法,以及检查所有 `non-transient` 字段的 hashCode 和 equals 实现。还将为所有 `non-final` 字段以及构造函数生成setter + +> 官方示例[@Data](https://projectlombok.org/features/Data) + +### @Value + +@Value 是 @Data 的变体版,默认情况下,所有字段都是 `private` 和 `final` 的,并且不会生成setter,默认情况下,`class` 本身也是 `final` 的,因为不可变性不是可以强制进入子类的东西 + +* 0.12.0版本加入稳定的该功能 + +> 官方示例[@Value](https://projectlombok.org/features/Value) + +### @Builder + +* 对成员注解,则它必须是构造函数或方法 +* 对类(class)注解,那么将生成一个私有构造函数,所有字段都作为参数(就好像类上有@AllArgsConstructor(access = AccessLevel.PRIVATE)),并且这个构造函数已经被@Builder注释了 + +>注意,只有当您没有编写任何构造函数,也没有添加任何显式的@XArgsConstructor注释时,才会生成这个构造函数。在这些情况下,lombok将假定存在一个all-args构造函数,并生成使用它的代码;这意味着如果没有这个构造函数,就会出现编译器错误。 + +> 官方示例[@Builder](https://projectlombok.org/features/Builder) + +### @SneakyThrows + +* 捕获或抛出方法主体中语句声明它们生成的任何已检查异常 +* SneakyThrows不会下沉,封装到RuntimeException中,或者以其他方式修改列出的已检查异常类型的任何异常 + +```java +// lombok 写法 +@SneakyThrows(UnsupportedEncodingException.class) +public void utf8ToString(byte[] bytes) { + return new String(bytes, "UTF-8"); +} + +// 等价于下面写法 +public void utf8ToString(byte[] bytes) { + try { + return new String(bytes, "UTF-8"); + } catch (UnsupportedEncodingException $uniqueName) { + throw useMagicTrickeryToHideThisFromTheCompiler($uniqueName); + // This trickery involves a bytecode transformer run automatically during the final stages of compilation; + // there is no runtime dependency on lombok. + } +} +``` + +> 官方示例[@SneakyThrows](https://projectlombok.org/features/SneakyThrows) + +### @Synchronized + +@Synchronized几乎与将“synchronized”关键字放在方法上完全一样,只不过它将同步到一个私有内部对象上,这样其他不在您控制之下的代码就不会通过锁定自己的实例来干扰线程管理 +* 对于 **非静态** 方法,使用一个名为 `$lock` 的字段 +* 对于 **静态** 方法,则注释会锁定名为 `$LOCK` 的静态字段 + +> 官方示例[@Synchronized](https://projectlombok.org/features/Synchronized) + +### @Getter(lazy=true) + +适用于那些计算占用大量 CPU,或者占用较大内存时,该注解很有用 + +> 官方示例[@GetterLazy](https://projectlombok.org/features/GetterLazy) + +### @Log + +注解路径`lombok.extern.java`路径下,相关的 Log 有已下几种 + +* @CommonsLog + ```java + private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); + ``` +* @Flogger + ```java + private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); + ``` +* @JBossLog + ```java + private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class); + ``` +* @Log + ```java + private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName()); + ``` +* @Log4j + ```java + private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class); + ``` +* @Log4j2 + ```java + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); + ``` +* @Slf4j + ```java + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); + ``` +* @XSlf4j + ```java + private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class); + ``` + +> 官方示例[@log](https://projectlombok.org/features/log) + +## 实验注解 + +Lombok提供实验性注解,请根据实际情况取舍,`org.projectlombok.lombok`包 `lombok.experimental`路径下 + +### @Accessors + + + +### @ExtensionMethod + + + +### @FieldDefaults + + + +### @Delegate + + + +### @Wither + + + +### onX + +* onMethod +* onConstructor +* onParam + +### UtilityClass + + + +### Helper + + + +### FieldNameConstants + + + +### SuperBuilder + + + +## 进阶配置 + +[Configuration system](https://projectlombok.org/features/configuration) + +## 其他 + +如果在对Android项目进行升级使用 Lombok 代替原来的写法,在很大程度上还是会遇到如下截图问题 +![lombok-error](https://res.cloudinary.com/incoder/image/upload/v1567324868/blog/lombok-error.png) +根据提示项目已经开启了 Annotation Processors,但是在每次打开项目都会提示错误信息 + +### 解决方法 + +Setting for all projects + +1. File -> Other Settings -> Settings for new projects -> Build, Execution, Deployment -> Compiler ->Annotation Processors +2. Enable Annotation Processing +3. Click Apply +4. Restart Your Android studio + +一些旧项目还需要额外的一些操作 + +1. 删除项目根路径下的 `yourProject.iml` 文件以及 `.idea` 目录 或者你可以 File -> Invalidate Caches / Restart... 操作 +2. 重新打开项目 + +>参考[issues264](https://github.com/mplushnikov/lombok-intellij-plugin/issues/264) diff --git a/source/_posts/mac-bash.md b/source/_posts/mac-bash.md new file mode 100644 index 000000000..ef72c2ffc --- /dev/null +++ b/source/_posts/mac-bash.md @@ -0,0 +1,152 @@ +--- +title: 应该知道的系统环境配置文件 +date: 2018-11-24 15:27:20 +categories: macOS +tag: [Shell] +--- + +在计算机操作系统中`Shell`是用户与操作系统交互的媒介,而`bash`作为目前`Linux\macOS`系统中最常用的`Shell`,它支持的`startup`文件也并不单一,甚至让人感到费解,以下就是对Shell的学习 + +Shell:在计算机中,值“为用户提供用户界面”的软件,通常指的是 **命令行界面** 的解析器。一般来说,`Shell`指操作系统中提供访问内核所提供的服务程序。 + + + +通常将`Shell`分为两类 +* 命令行:提供一个命令行界面(CLI) +* 图形界面:提供一个图形用户界面(GUI) + +![linux_system](https://upload.wikimedia.org/wikipedia/commons/thumb/b/be/The_general_structure_of_a_Linux_system.jpeg/250px-The_general_structure_of_a_Linux_system.jpeg) + +在PC桌面领域,不同的操作系统都有自己的`Shell`,截止2018.10主流的操作系统市场占有率,Windows(78.04%),OS X(13.73%),Unknown(5.44%),Linux(1.64%),Chrome(1.15%),数据来源于[statcounter](http://gs.statcounter.com/os-market-share/desktop/worldwide/#monthly-201710-201810); + +这些操作系统中都有自己独特的`Shell`命令,在不同的系统版本中,命令工具也是不完全相同,例如: +* Windows:Windows CE、Windows NT常用[`cmd.exe`](https://en.wikipedia.org/wiki/Cmd.exe);Windows 10中常用[`PowerShell`](https://zh.wikipedia.org/wiki/Windows_PowerShell) +* OS X:默认[`bash`](https://zh.wikipedia.org/wiki/Bash),除此之外还提供了[`tcsh`](https://zh.wikipedia.org/wiki/Tcsh)、[`zsh`](https://zh.wikipedia.org/wiki/Z_shell)和[`ksh`](https://zh.wikipedia.org/wiki/Korn_shell) +* Linux:`/etc/shells`路径下,`/bin/sh`,`/bin/bash`,`/bin/csh`等应用 +> 更详细的请查阅[维基百科](https://zh.wikipedia.org/wiki/%E6%AE%BC%E5%B1%A4#%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%95%8C%E9%9D%A2%EF%BC%88CLI%EF%BC%89%E6%AE%BC%E5%B1%A4) + +## Configuration Files +| 文件 | sh | ksh | csh | tcsh | bash | zsh | +| --------- | --- | --- | --- | --- | --- | --- | +| /etc/.login | --- | --- | login | login | --- | --- | +| /etc/csh.cshrc | --- | --- | yes | yes | --- | --- | +| /etc/csh.login | --- | --- | login | login | --- | --- | +| ~/.tcshrc | --- | --- | --- | yes | --- | --- | +| ~/.cshrc | --- | --- | yes | yes | --- | --- | +| ~/etc/ksh.kshrc | --- | int. | --- | --- | --- | --- | +| /etc/sh.shrc | int. | --- | --- | --- | --- | --- | +| $ENV (typically ~/.kshrc) | int. | int. | --- | --- | int. | --- | +| ~/.login | --- | --- | login | login | --- | --- | +| ~/.logout | --- | --- | login | login | --- | --- | +| /etc/profile | login | login | --- | --- | login | login | +| ~/.profile | login | login | --- | --- | login | login | +| ~/.bash_profile | --- | --- | --- | --- | login | --- | +| ~/.bash_login | --- | --- | --- | --- | login | --- | +| ~/.bash_logout | --- | --- | --- | --- | login | --- | +| ~/.bashrc | --- | --- | --- | --- | int.+n/login | --- | +| /etc/zshenv | --- | --- | --- | --- | --- | yes | +| /etc/zprofile | --- | --- | --- | --- | --- | login | +| /etc/zshrc | --- | --- | --- | --- | --- | int. | +| /etc/zlogin | --- | --- | --- | --- | --- | login | +| /etc/zlogout | --- | --- | --- | --- | --- | login | +| ~/.zshenv | --- | --- | --- | --- | --- | yes | +| ~/.zprofile | --- | --- | --- | --- | --- | login | +| ~/.zshrc | --- | --- | --- | --- | --- | int. | +| ~/.zlogin | --- | --- | --- | --- | --- | login | + +* yes:表示shell在启动时始终读取文件 +* login:表示如果shell是登录shell,则读取文件 +* n/login:表示如果shell不是登录shell,则读取文件 +* int.:表示如果shell是交互式的,则读取文件 + +>更详细的介绍请查阅[维基百科](https://en.wikipedia.org/wiki/Unix_shell#Configuration_files) + +关于常用`Shell`,执行流程如下图: +![flow](https://res.cloudinary.com/incoder/image/upload/v1543141362/blog/flow.png) + +## startup文件 +`bash`作为目前`Linux`,`macOS(默认bash命令)`系统中最常用的`shell`,通过上面的表格,我们可以知道macOS系统中,`bash`主要由以下文件 +* /etc/profile:The systemwide initialization file, executed for login shells +* ~/.profile: +* ~/.bash_profile:The personal initialization file, executed for login shells +* ~/.bash_login: +* ~/.bash_logout:The individual login shell cleanup file, executed when a login shell exits +* ~/.bashrc:The individual per-interactive-shell startup file + +我们看看在macOS系统中,bash的startup文件是如何进行加载 + +{% note info %} +注意: +* `/etc/profile`和`/etc/paths`是系统级别,系统启动后就会加载,后面的配置文件是当前用户级的环境变量 +* 如果`~/.bash_profile`存在,后面几个文件就会忽略不读,不在时,才会以此类推读取后面的文件 +* `~/.bashrc`没有上述规则,他始终加载,它是在`bash shell`打开的时候载入的 +{% endnote %} + +## 特点 +`bash`的两种属性,即 **“交互”** 与 **“登录”**,按照`bash`是否与用户进行交互,可将其分为 **“交互式”** 与 **“非交互式”**;按照`bash`是否被用户登录,又可将其分为 **“登录shell”** 与 **“非登录shell”** + +### 交互式与非交互式 +* 交互式:shell的一种运行模式,交互式shell等待用户输入命令,并且立即执行,然后将结果反馈给用户。整个流程:登录——>执行命令——>退出。当你退出后,这个shell就终止 +* 非交互式:shell的另一种运行模式,它专门用来执行预先设定的命令。这种模式下,shell不予用户进行交互,而是读取存储在脚本文件中的命令并执行它们。当它读取到文件结尾,这个shell就终止 + +### 登录shell与非登录shell +* 登录shell: + * 用户通过输入用户名/密码(或者证书认证)后启动的shell; + * 通过带有`-l|--login`参数的`bash`命令启动的shell +例如:系统启动,远程启动,使用`su -`切换用户,通过`bash --login`命令启动的bash等 +* 非登录shell:以上情况除外基本就是 **“非登录shell”** +例如:从图形化界面启动终端,使用`su -`切换用户,通过`bash`命令启动bash等 + +### 主要区别 +* 使用`logout`退出`登录shell`,使用`exit`退出`非登录shell`。 +* 其实`exit`命令会判断当前shell的登录属性,并且分别调用`logout`或`exit`指令 +* **登录shell**和**非登录shell**的主要区别在于启动shell时所执行的startup文件不同;登录shell执行的startup文件为`~/.bash_profile`,而**非登录shell**执行的startup文件为`~/.bashrc` + +## 总结 + +### Path语法 +```bash +# 中间使用冒号分隔 +export PATH=$PATH::::------: +``` + +### 环境变量设置 + +#### 全局设置 +* `/etc/paths`:全局环境变量设置,建议修改此文件 +* `/etc/profile`:不建议修改此文件,全局配置,不管是哪个用户,登录时都会读取此文件 +* `/etc/bashrc`:一般在这个文件中添加系统级别环境变量,全局配置,bash shell执行时,不管是何种方式,都会读取此文件 + +#### 单用户设置 +* `~/.bash_profile`:添加用户级环境变量 +例如:设置`ANDROID_HOME`到PATH + ```bash + export ANDROID_HOME=/Users/shaoc/Library/Android/sdk + export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH + ``` +* `~/.bashrc` 同上 +一般重启shell设置就会生效,如果想立刻生效,则可执行下面的语句: + ```bash + source 相应的文件 + ``` + +#### zsh中配置环境变量 +在安装 `oh my zsh`后,`.bash_profile`文件中的环境变量就无法起到作用,因为终端默认启动的是`zsh`,而不是`shell`,所以无法加载 + +* 解决方法 +在`~/.zshrc`配置文件中,增加对`.bash_profile`的引用: + ```bash + source ~/.bash_profile + ``` + + `.bash_profile`文件示例: + ```bash + export ANDROID_HOME=/Users/blade/Library/Android/sdk + export GRADLE_HOME=/Users/blade/Documents/DevTools/Gradle/gradle-4.6 + export FLUTTER_HOME=/Users/blade/Documents/DevTools/flutter + export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$GRADLE_HOME/bin:$FLUTTER_HOME/bin:$PATH + ``` + +## 附录 +* [原关于“.bash_profile”和“.bashrc”区别的总结](https://blog.csdn.net/sch0120/article/details/70256318) +* [Mac环境变量配置](https://hao5743.github.io/2017/06/28/2017-06-28/) \ No newline at end of file diff --git a/source/_posts/mac-download.md b/source/_posts/mac-download.md new file mode 100644 index 000000000..0edefa26a --- /dev/null +++ b/source/_posts/mac-download.md @@ -0,0 +1,148 @@ +--- +title: Aria2 之 macOS +date: 2018-12-12 02:14:59 +categories: macOS +tag: [Download] +--- + +## Aria2 是什么 +Aria2 是一款支持多种协议的 **轻量级命令行** 下载工具。有以下特性: +* 多线程连线:Aria2 会自动从多个线程下载文件,并充分利用你的带宽; +* 轻量:运行时不会占用过多资源,根据官方介绍,内存占用通常在 4MB~9MB,使用 BitTorrent 协议,下行速度 2.8MB/s 时 CPU 占用率约 6%; +* 全功能 BitTorrent 客户端; +* 支持 RPC 界面远程控制 + + + +## Aria2 安装 +```bash +brew install aria2 +``` + +> [Homebrew](https://brew.sh)是一款自由及开放源代码的软件包管理系统,用以简化Mac OS X系统上的软件安装过程,以Ruby语言写成,默认安装在`/usr/local` + +## Aria2 配置 + +```bash +# 进入~路径 +cd ~ +# 创建.aria2文件夹 +mkdir .aria2 +# 创建aria2.conf配置文件 +touch aria2.conf +``` + +复制以下内容保存在`aria2.conf`文件中,**修改** `dir=/Users/blade/Downloads`路径即可 + +``` +#用户名 +#rpc-user=user +#密码 +#rpc-passwd=passwd +#上面的认证方式不建议使用,建议使用下面的token方式 +#设置加密的密钥 +#rpc-secret=token +#允许rpc +enable-rpc=true +#允许所有来源, web界面跨域权限需要 +rpc-allow-origin-all=true +#允许外部访问,false的话只监听本地端口 +rpc-listen-all=true +#RPC端口, 仅当默认端口被占用时修改 +rpc-listen-port=6800 +#最大同时下载数(任务数), 路由建议值: 3 +max-concurrent-downloads=5 +#断点续传 +continue=true +#同服务器连接数 +max-connection-per-server=5 +#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要 +min-split-size=10M +#单文件最大线程数, 路由建议值: 5 +split=10 +#下载速度限制 +max-overall-download-limit=0 +#单文件速度限制 +max-download-limit=0 +#上传速度限制 +max-overall-upload-limit=0 +#单文件速度限制 +max-upload-limit=0 +#断开速度过慢的连接 +#lowest-speed-limit=0 +#验证用,需要1.16.1之后的release版本 +#referer=* +#文件保存路径, 默认为当前启动位置 +dir=/Users/blade/Downloads +#文件缓存, 使用内置的文件缓存, 如果你不相信Linux内核文件缓存和磁盘内置缓存时使用, 需要1.16及以上版本 +#disk-cache=0 +#另一种Linux文件缓存方式, 使用前确保您使用的内核支持此选项, 需要1.15及以上版本(?) +#enable-mmap=true +#文件预分配, 能有效降低文件碎片, 提高磁盘性能. 缺点是预分配时间较长 +#所需时间 none < falloc ? trunc << prealloc, falloc和trunc需要文件系统和内核支持 +file-allocation=prealloc +``` + +### 开启 Aria2 +终端中输入,其中xxx是你的电脑用户名 +```bash +aria2c --conf-path="/Users/xxx/.aria2/aria2.conf" -D +``` + +### Aria2 开机自启 +1. 创建`aria2.plist`文件 + ```bash + cd ~/Library/LaunchAgents + touch aria2.plist + ``` +2. 修改`aria2.plist`文件内容,其中``中的值改为自己电脑上 aria2c 命令的路径,可以在终端输入which aria2c查看,将WorkingDirectory后面的``中的值改为自己的下载路径 + ```bash + + + + + KeepAlive + + RunAtLoad + + Label + aria2 + ProgramArguments + + /usr/local/bin/aria2c + + WorkingDirectory + /Users/blade/Downloads + + + ``` + +### 启用Web +其实,如果你喜欢使用命令来操作,那么此步可跳过 +```bash +# 获取项目代码 +git clone https://github.com/ziahamza/webui-aria2 +# 打开 index.html 文件 +cd webui-aria2/docs +open index.html +``` + +## 其他 + +### 进行brew更新警告 +警告内容:`Unbrewed header files were found in /usr/local/include ...` +原因:系统中已存在下面列表中包含的包内容不是通过`brew`进行安装 +解决方法:删除那些文件就可以了 +```bash +# 或者获取sudo权限删除 +sudo rm -rf ‘/usr/local/bin/node’ +# 重新安装node +brew install node +``` + +## 附录 +* [Mac安装使用aria2,AriaNg下载百度网盘资源](https://blog.tearth.me/mac_aria2_ariang/) +* [如何配置 Aria2 来进行文件下载](https://mofiter.com/2018/08/19/%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE-Aria2-%E6%9D%A5%E8%BF%9B%E8%A1%8C%E6%96%87%E4%BB%B6%E4%B8%8B%E8%BD%BD/) +* [Aria2 - 下载神器](https://mac-setup.wildflame.org/aria_2/readme.html) +* [Aria2 命令使用参考文档](https://github.com/erasin/notes/blob/master/linux/soft/aria2.md) \ No newline at end of file diff --git a/source/_posts/mac-init.md b/source/_posts/mac-init.md new file mode 100644 index 000000000..d6ec0e5ea --- /dev/null +++ b/source/_posts/mac-init.md @@ -0,0 +1,179 @@ +--- +title: MacBook Pro 初始化 +date: 2018-11-10 09:44:46 +categories: macOS +tag: [Build, Exp] +--- + +今天拿到了一辆跑车 MBP,虽然不是顶配,也能算上中等吧,废话不啰嗦,上来就是一顿操作猛如虎,最终效果就是唬 + +跑车的一些零配件来源地[Awesome Mac](https://github.com/jaywcjlove/awesome-mac/blob/master/README-zh.md),[MacWK](https://www.macwk.com/) 一些破解软件集合地 + +软件的安装,这里不再赘述,这里主要对常用开发软件的配置进行记录 + + + +## JDK + +作为Android开发者,JDK的安装那是少不了 + +### 下载 + +在Oracle 官网下载所需JDK 版本,这里举例:[JDK1.8.0_191](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) + +### 安装 + +此处省略,简单的安装步骤 + +### 配置 + +以下**命令**相关操作,均在自带系统**终端**应用或者自己安装的其他终端命令工具 + +```bash +# 查看安装的Java版本 +java -version +# 编辑profile文件 +sudo vim /etc/profile +# 在打开的 profile 文件中,最下面加入以下文本,添加完成后,保存退出 +JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/" + +CLASS_PATH="$JAVA_HOME/lib" + +PATH=".:$PATH:$JAVA_HOME/bin" + +# 使配置生效 +source /etc/profile +# 查看JAVA_HOME是否正确 +echo $JAVA_HOME +``` + +![jdk-command](https://res.cloudinary.com/incoder/image/upload/v1541968115/blog/mac-jdk-comand.png) + +![jdk-config](https://res.cloudinary.com/incoder/image/upload/v1541968115/blog/mac-jdk-config.png) + +> 注意: +> 1. `JAVA_HOME` 中 `jdk1.8.0_191.jdk` 是自己安装对应版本的文件夹,可以在Finder中,快捷键:Command + Shift + G,输入: `/Library/Java/JavaVirtualMachines/` ,最终得到对应的文件夹名,如: `jdk1.8.0_191.jdk` +> 2. vim模式下,输入“i”:表示,插入,“esc”:表示退出编辑模式,“:wq!”:表示保存并退出 + +## MySQL + +### 下载 + +官方下载地址:https://dev.mysql.com/downloads/ + +![mac-mysql-downlaod](https://res.cloudinary.com/incoder/image/upload/v1582517702/blog/mac-mysql-downlaod.png) + +### 安装 + +![mac-mysql-install](https://res.cloudinary.com/incoder/image/upload/v1582523133/blog/mac-mysql-install.gif) + +我这里选择自定义设置密码,请记住你设置的密码,最好是大小写+数字+字符的组合方式 + +### 登录 + +```mysql +# 使用 root 账号登录 MySQL +mysql -u root -p +# 出现下图中的MySQL,表示成功连接 MySQL +``` + +![mac-login-mysql](https://res.cloudinary.com/incoder/image/upload/v1582523478/blog/mac-login-mysql.png) + +### 卸载 + +![mac-mysql-uninstall](https://res.cloudinary.com/incoder/image/upload/v1582517706/blog/mac-mysql-uninstall.png) + +## Git + +直接在自带系统**终端**应用中,输入 `git --version` ,由于之前并没有安装,系统会提示,直接同意并安装即可 + +### GitHub配置 + +```bash +# 查看本地是否生成过秘钥,如果该文件夹不存在,则表示未生成过秘钥 +cd ~/.ssh +# 生成一个github的秘钥,这里github可以根据喜好自己命名(默认.ssh路径,不添加密码等操作,直接三次回车,即可生成秘钥) +ssh-keygen -t rsa -C "github" +# 查看公钥 +cat ~/.ssh/id_rsa.pub +# 复制公钥添加到GitHub的SSH设置中,这里省略操作截图步骤 +``` +![mac-github-config](https://res.cloudinary.com/incoder/image/upload/v1542034940/blog/mac-github-config.png) + +## Gradle配置 + +* 下载地址:[官网](http://services.gradle.org/distributions),下载-all版本 +* 设置GRADLE_HOME路径 + ``` bash + # 打开.bash_profile文件 + open -e .bash_profile + # Gradle_HOME环境设置,并保存 + GRADLE_HOME=/Users/blade/Documents/DevTools/Gradle/gradle-4.6 + export GRADLE_HOME + export PATH=$PATH:$GRADLE_HOME/bin + # 配置文件生效 + source ~/.bash_profile + # 验证配置 + gradle -version + ``` + + >如果提示The file /Users/blade/.bash_profile does not exist.则在根路径下创建 `.bash_profile` 文件 + >执行命令 `touch .bash_profilesss` + + ![gradle-config](https://res.cloudinary.com/incoder/image/upload/v1541968116/blog/mac-gradle-config.png) + +## Keka + +对于 macOS 上的文件解压缩工具,有[The Unarchiver](),[BetterZip](https://macitbetter.com/),[Keka](https://www.keka.io/zh-cn/)等,我这里使用 keka,毕竟开源:https://github.com/aonez/Keka,真香 + +```bash +# 安装 +brew cask install keka +# 卸载 +brew cask zap keka +``` + +> [Mac 压缩 / 解压缩工具解决方案](https://sspai.com/post/46943) + +## OpenInTerminal + +OpenInTerminal 是在 Finder 上的一个扩展工具,能够快速在当前位置已命令行或者指定的编辑器打开,非常方便,对于 OpenInTerminal 和 OpenInTerminal-Lite,OpenInEditor-Lite 的区别 + +| Features | OpenInTerminal | OpenInTerminal-Lite & OpenInEditor-Lite | +| --- | --- | --- | +| Support Terminal, [iTerm](https://www.iterm2.com/), [Hyper](https://github.com/zeit/hyper), [Alacritty](https://github.com/jwilm/alacritty) and [kitty](https://sw.kovidgoyal.net/kitty/). | ✅ | ✅ | +| Support TextEdit, [Visual Studio Code](https://code.visualstudio.com/), [VSCode Insiders](https://code.visualstudio.com/insiders/), [Atom](https://atom.io/), [Sublime Text](https://www.sublimetext.com/), [VSCodium](https://github.com/VSCodium/vscodium), [BBEdit](https://www.barebones.com/products/bbedit/), [TextMate](https://macromates.com), [CotEditor](https://coteditor.com/), [MacVim](https://github.com/macvim-dev/macvim) and [JetBrains](https://www.jetbrains.com/)(AppCode, CLion, GoLand, IntelliJ IDEA, PhpStorm, PyCharm, RubyMine, WebStorm). | ✅ | ✅ | +| Set to open a new tab or window. | ✅ | ✅ | +| Support English, Chinese, French, Russian, Italian and Spanish. | ✅ | ✅ | +| Copy path of the selected file or Finder window to Clipboard | ✅ | ❌ | +| GUI preferences | ✅ | ❌ | +| Support keyboard shortcuts. | ✅ | ❌ | +| Support Dark Mode. | ✅ | ❌ | + +### 安装 + +* openinterminal + ```bash + brew cask install openinterminal + ``` +* openinterminal-lite & openineditor-lite + ```bash + brew cask install openinterminal-lite + brew cask install openineditor-lite + ``` + +### 配置 + +这里主要是对应用进行授权 + +* openinterminal + System Preferences(系统偏好设置) -> Extensions(扩展) -> Finder Extensions(访达扩展) +* openinterminal-lite & openineditor-lite + 拖拽 openinterminal-lite & openineditor-lite 到你的 Finder 的状态栏上 + +## macOS 快捷键 + +顺带记录自己常用的快捷键吧🙃,其他用的时候在边用边查找吧 + + +>[Mac 键盘快捷键](https://support.apple.com/zh-cn/HT201236) \ No newline at end of file diff --git a/source/_posts/mac-item2.md b/source/_posts/mac-item2.md new file mode 100644 index 000000000..9d92a5cfc --- /dev/null +++ b/source/_posts/mac-item2.md @@ -0,0 +1,433 @@ +--- +title: iTerm2 日常 +date: 2020-08-05 00:32:10 +categories: macOS +tag: iTerm2 +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1596547673/blog/logo2x.jpg) + +iTerm2是一款优秀强大的第三方终端,相信用 Mac 的开发者,一定听过或者用过 iTerm2 这款终端应用,如果你还没使用过,没关系那么本篇文章就带你了解学习 iTerm2 中的一些常用操作,来提高你的工作效率 + + + +## 安装配置 + +### 下载 + +[iTerm2官网](https://www.iterm2.com) + +> 注意: +>系统自带的终端默认使用`bash`;iTerm2默认使用`zsh`,因此两者切换如下命令 +```bash +# 安装完iTerm 可使用如下命令来切换 +chsh -s /bin/[zsh | bash] +``` + +### 配置 + +### 基础设置 + +1. 默认应用 +MenuBar -> iTerm2 -> Make iTerm2 Default Term +![iterm2-default](https://res.cloudinary.com/incoder/image/upload/v1541968115/blog/mac-iterm2-default.png) + +2. 全局热键 +MenuBar -> iTerm2 -> preference -> Keys -> Show/hide iTerm2 with a system-wide hotkey +输入设置的快捷键,这里使用`⌘,` +![iterm2-hotkey](https://res.cloudinary.com/incoder/image/upload/v1541968115/blog/mac-iterm2-hotkey.png) + +### 安装Oh my zsh + +* 方式一:crul + ```bash + sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" + ``` +* 方式二:wget + ```bash + sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)" + ``` + ![zsh-install](https://res.cloudinary.com/incoder/image/upload/v1541968115/blog/mac-zsh-install.png) + +#### PowerLine + +```bash +pip install powerline-status --user +``` +> 如果提示: +command not found: pip +先执行,再执行上面的字体安装命令 +```bash +sudo easy_install pip +``` + +#### 安装PowerFonts + +为避免后续的使用中,可能会遇到字符乱码的问题,因此安装字体 + +字体库需要首先将项目`clone`到本地,然后执行源码中的`install.sh`,根据自己的喜好存放在指定的位置 +```bash +# 进入Documents目录 +cd Documents +# 创建文件夹PowerFonts +mkdir PowerFonts +# 进入PowerFonts目录 +cd PowerFonts +# clone源码 +git clone https://github.com/powerline/fonts.git --depth=1 +# 进入fonts目录 +cd fonts +# 执行安装脚本 +./install.sh +``` + +#### 设置字体及背景 + +* 设置字体 +MenuBar -> iTerm2 -> Preferences -> Profiles -> Text -> Change Font,选择`Meslo LG`字体,L,M,S风格,看个人喜好,这里选择`Meslo LG S Powerline` +![iterm2-font](https://res.cloudinary.com/incoder/image/upload/v1541968115/blog/mac-iterm2-font.png) + +* 背景设置 +![iterm2-presets](https://res.cloudinary.com/incoder/image/upload/v1541968117/blog/mac-iterm2-presets.png) + +#### 修改主题 + +```bash +# 打开.zshrc隐藏文件 +vim ~/.zshrc +# 修改ZSH_THEME为agnoster +ZSH_THEME="agnoster" +``` +>默认:ZSH_THEME="robbyrussell" + +#### 辅助 + +* 高亮插件 + ```bash + cd ~/.oh-my-zsh/custom/plugins/ + git clone https://github.com/zsh-users/zsh-syntax-highlighting.git + vim ~/.zshrc + # 添加zsh-syntax-highlighting到plugins中,放在git后面 + plugins=( + git + zsh-syntax-highlighting + ) + # 文件最后添加,然后保存并退出 + source ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh + ``` + 最后,对配置文件进行生效处理 + ```bash + source ~/.zshrc + ``` +* 命令补全 +安装步骤和上面的高亮插件一致 + ```bash + cd ~/.oh-my-zsh/custom/plugins/ + + git clone https://github.com/zsh-users/zsh-autosuggestions + + vi ~/.zshrc + ``` +* 设置背景图 +iTerm2 -> Preferences -> Profiles -> Window -> BackGround Image + +## 主题选择 + +Oh my zsh 本身也包含了很多主题,而我比较喜欢的一款 [powerlevel10k](https://github.com/romkatv/powerlevel10k) 的主题,非常的简洁,满足我的装 X 的需求的同时,极其简单的设置也是我对其爱不释手。 + +之前最早使用的是 [powerlevel9k](https://github.com/Powerlevel9k/powerlevel9k),我的博客里面一些终端的截图就是 powerlevel9k,但随着我的需求越来越多以及加载的插件也越来越多,我不能忍受 iTerm2 在使用 时的效率问题,然后就看到了powerlevel10k,它是在powerlevel9k 的基础上迭代的,大大提高了响应效率和更加简洁的配置等 + +### 安装 + +官方提供了多种安装方式(Manual,Oh My Zsh,Homebrew 等等),选择你熟系的方式进行安装,我这里使用 [Oh My Zsh](https://github.com/romkatv/powerlevel10k#oh-my-zsh) 方式安装,选择了 gitee 镜像地址进行下载 + +```bash +# 1. 下载主题 +git clone --depth=1 https://gitee.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k +# 2. 在 .zshrc 文件中设置主题 +vim ~/.zshrc +# 3. 大概 19 行,设置 ZSH_THEME 参数的值为 powerlevel10k/powerlevel10k +ZSH_THEME="powerlevel10k/powerlevel10k" +``` + +>默认安装路径:/Users/``/.oh-my-zsh/custom/themes/powerlevel10k + +### 更新 + +当初选择的什么安装方式,就使用什么方式进行更新,我这里以 [Oh My Zsh](https://github.com/romkatv/powerlevel10k#FAQ) 方式进行更新 + +```bash +git -C ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k pull +``` + +### 配置 + +只需要在终端()或者 iTerm2 命令窗口中输入 `p10k configure` 即可,然后根据提示选择即可完成配置,我比较懒,就用的 `Pure` 样式 + +### 问题 + +#### VS Code + +在 VS Code Terminal 中显示不出来图标或者显示乱码异常等问题,先设置 VS Code 字体看看是否能解决,如果不能再进行排查 + +Open File → Preferences → Settings, 在搜索框中输入`terminal.integrated`,字体设置为`MesloLGS NF`. + +## 常用工具 + +### Homebrew + +* [Homebrew](https://brew.sh) 是一款自由及开放源代码的软件包管理系统,用以简化macOS系统上的软件安装过程,最初由马克斯·霍威尔(Max Howell)写成 +* [Homebrew Cask](https://github.com/Homebrew/homebrew-cask),它是一套建立在 Homebrew 基础之上软件安装命令行工具,是 Homebrew 的扩展 + +#### 官方 + +>如果你使用的官方安装教程,需要切换 brew 的镜像源,可参考 [专治各种网络不服](https://incoder.org/2020/02/27/fuck-gfw) 文章 + +##### 安装 + +在你的终端(Terminal/iTerm2)运行如下脚本 +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" +``` + +##### 卸载 + +```bash +ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)" +``` + +#### 国内(推荐) + +>按照命令行上面的提示,进行安装 + +##### 安装 + +```bash +/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" +``` + +##### 卸载 + +```bash +/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/HomebrewUninstall.sh)" +``` + +#### 常用命令 + +对于 brew 和 brew cask 的命令基本一致,比如 +* 【brew】:brew install [包名] +* 【brew cask】:brew cask install [包名] + +##### brew 基础命令 + +```bash +# 安装指定包应用,如:brew install wget +brew install [包名] +# 重新安装指定应用,如:brew reinstall wget +brew reinstall [包名] +# 卸载指定包,如:brew uninstall wget +brew uninstall [包名] +# 列出已安装的软件 +brew list +# 全部更新过时软件,如:brew upgrade +brew upgrade +# 指定更新过时的软件,如:brew upgrade wget +brew upgrade [包名] +# 用浏览器打开brew的官方网站 +brew home +# 显示当前软件信息,如:brew info wget +brew info [包名] +# 显示包依赖,如:brew deps wget +brew deps [包名] +# 清理所有包的旧版本 +brew cleanup +# 清理指定包的旧版本 +brew cleanup [包名] +# 查看可清理的旧版本包,不执行实际操作 +brew cleanup -n +# 查找指定包 +brew search [包名] +# 查看缓存路径 +brew --cache +``` + +##### brew service + +```bash +# 查看使用brew安装的服务列表 +brew services list +# 启动服务(仅启动不注册) +brew services run formula|--all +# 启动服务,并注册 +brew services start formula|--all +# 停止服务,并取消注册 +brew services stop formula|--all +# 重启服务,并注册 +brew services restart formula|--all +# 清除已卸载应用的无用的配置 +brew services cleanup +``` + +### Nmap + +端口扫描必备工具 +```bash +# 安装 nmap 工具 +brew install nmap +``` + +![nmap](https://res.cloudinary.com/incoder/image/upload/v1605693920/blog/nmap.png) + +## 常用快捷键 + +### 标签 + +1. 新建标签:⌘(command) + t +2. 关闭标签:⌘(command) + w +3. 切换标签:⌘(command) + 数字 ⌘(command) + 左右方向键 +4. 切换全屏:⌘(command) + enter +5. 查找:⌘(command) + f + +### 分屏 + +1. 垂直分屏:⌘(command) + d +2. 水平分屏:⌘(command) + ⇧(shift) + d +3. 切换屏幕:⌘(command) + ⌥(option) + 方向键 ⌘(command) + [ 或 ⌘(command) + tab 所在的数字] +4. 查看历史命令:⌘(command) + ; +5. 查看剪贴板历史:⌘(command) + ⇧(shift) + h + +### 其他 + +1. 清除当前行:⌘(command) + u +2. 清屏:⌘(command) + r +3. 列出剪切板历史:⌘(command) + ⇧(shift) + h +4. 命令快照(很实用的一个功能):⌘(command) + ⌥(option) + b + >大概只能记录 30s 左右的操作 + +## 常见问题 + +### 文本乱码 + +在一开始使用macOS就已经安装iTerm2来代替了系统自带的Terminal应用,毕竟颜值是决定要不用长期使用的重要因素 + +iTerm2对应的配置文件:`.zshrc`,Terminal对于的配置文件:`.bash_profile` 或 `.bashrc` + +* 问题:iTerm2查看本地文件,能正常显示,无乱码,但查看服务器上文件,出现乱码 +* 原因:本地iTerm2终端和服务器字符集不一致,造成乱码,macOS默认Terminal应用是`utf-8`,而iTerm2默认没有设置`utf-8`编码 +* 解决办法:给本地的`.zshrc`设置字符集编码 + ```bash + # 使用vim打开.zshrc文件 + vim ~/.zshrc + # 在文本内容末尾添加以下两行内容进行字符编码设置 + export LC_ALL=en_US.UTF-8 + export LANG=en_US.UTF-8 + # 保存文件内容,退出vim模式,并使刚刚设置的内容生效 + source ~/.zshrc + ``` + >帮助:可以在本地和服务器上分别使用`locale`命令来查看,本地和服务器的字符编码是否一致 + +### 结束指定进程 + +```bash +# 查看指定端口号 lsof -i:端口号 +lsof -i:8088 +COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME +java 72612 blade 18u IPv6 0x21ccddb0352361e5 0t0 TCP *:radan-http (LISTEN) +# kill指定进程 +kill -9 72612 +``` + +### 免密登录服务器 + +一图胜千言,请看图 + +![ssh-login](https://res.cloudinary.com/incoder/image/upload/v1559382897/blog/ssh-login.png) + +### 代理处理 + +在 Mac 系统上,使用 iTerm2 是一件很享受的过程,很多事情都可以通过命令行直接完成,但是一个致命的问题是,很多连接在国内环境下,异常忙,比如通过命令 clone 或处理 GitHub 上的项目,速度慢的让人抓狂,虽然电脑开启了代理(非全局),但视乎没有什么作用,针对此问题,需要让我们的终端也通过代理 + +1. install privoxy + ```bash + brew install privoxy + ``` +2. setting privoxy + ```bash + vim /usr/local/etc/privoxy/config + ``` +3. config privoxy + ```bash + listen-address 0.0.0.0:xxxx + forward-socks5 / localhost:1080 . + ``` + >0.0.0.0 可以让其他设备访问到,若不需要,请修改成用 127.0.0.1;xxxx是HTTP代理的默认端口; + >localhost:1080 是 SOCKS5(shadowsocks) 默认的地址,可根据需要自行修改,且注意不要忘了最后有一个空格和点号。 +4. start privoxy + ```bash + # 因没有安装在系统目录内,所以启动的时候需要打全路径 + sudo /usr/local/sbin/privoxy /usr/local/etc/privoxy/config + # 查看是否启动成功(1087 端口号换成自己的) + netstat -na | grep 1087 + # 看到有类似如下信息就表示启动成功了 + tcp4 0 0 *.1087 *.* LISTEN + ``` + 代理端口查看 + ![shadowsocks-proxy](https://res.cloudinary.com/incoder/image/upload/v1562848027/blog/shadowsocks-proxy.png) +5. use proxy + * temp proxy + 如果关闭终端标签页或窗口,功能就会失效 + - star proxy + ```bash + # 这里的端口号1087,换成你自己的 + export http_proxy='http://localhost:1087' + export https_proxy='http://localhost:1087' + ``` + - cancel proxy + ```bash + unset http_proxy + unset https_proxy + + ``` + * auto proxy + - setting ~/.bash_profile + ```bash + # 打开.bash_profile 文件 + vim ~/.bash_profile + # .bash_profile文件最后添加(1087 端口替换成你自己的) + export http_proxy='http://localhost:1087' + export https_proxy='http://localhost:1087' + # 保存文件 :wq 后,使配置生效 + source ~/.bash_profile + ``` + - 上面的方式也可以在文件(`.bash_profile`)中加入如下方法,使用时只需要在终端中输入`proxy_on`命令,关闭输入`proxy_off` + ```bash + function proxy_off(){ + unset http_proxy + unset https_proxy + echo -e "已关闭代理" + } + + function proxy_on() { + export no_proxy="localhost,127.0.0.1,localaddress,.localdomain.com" + export http_proxy="http://127.0.0.1:1087" + export https_proxy=$http_proxy + echo -e "已开启代理" + } + ``` +6. test + ```bash + # 已废弃,只能查看到当前的 IP 地址,其他信息请使用 curl cip.cc 命令 + curl ip.gs + ``` + ![proxy-config](https://res.cloudinary.com/incoder/image/upload/v1562849019/blog/proxy-config.png) + ```bash + curl cip.cc + ``` + ![proxy-config-info](https://res.cloudinary.com/incoder/image/upload/v1605687042/blog/proxy-config-info.png) + +## 参考 + +* [Mac终端-iTerm2使用](https://zhangmiao.cc/posts/e6bc65de.html) +* [Homebrew国内如何自动安装(国内地址)](https://zhuanlan.zhihu.com/p/111014448) +* [iTerm2 用法与技巧](https://lhajh.github.io/mac/2018/04/25/Iterm2-usage-and-skills.html) + diff --git a/source/_posts/mac-question.md b/source/_posts/mac-question.md new file mode 100644 index 000000000..eca2c4c42 --- /dev/null +++ b/source/_posts/mac-question.md @@ -0,0 +1,32 @@ +--- +title: MacBook Pro 疑难杂症 +date: 2020-11-13 02:04:46 +categories: macOS +tag: [Exp, iTerm2] +--- + +这是一篇记录使用macOS系统时遇到的一些疑难杂症 + +## macOS Big Sur + +在 2020.11.13 正式推送了 macOS Big Sur version 11.0.1 版本,这一个版本是改动比较大的版本,这里关于它的新特性就不做介绍了,有兴趣的请查看官方网站介绍 [Big Sur](https://www.apple.com.cn/macos/big-sur) + +![](https://res.cloudinary.com/incoder/image/upload/v1605885064/blog/macOS_Big_Sur.png) + + + +### Glance 失效 + +Glance 是一个快速预览增强,可以对一些文件进行快速预览,大大提高我们的日常效率,但该应用在 Big Sur 版本中不兼容,由于作者已入职 Apple,且对项目做了归档,不在维护,因此该问题依旧没有解决,可以使用一个付费的应用[iPreView](https://apps.apple.com/cn/app/ipreview-powerful-quick-look/id1519213509?l=en&mt=12)来满足当前需要 + +>[Glance 在 Big Sur 系统中失效](https://v2ex.com/t/725909) + +## AirPods 异常 + +在 AirPods 使用过程中,发现有时候耳机并不能正常工作。通常情况下,我会断开与 macOS 的连接,重新连接,如果还是不能正常工作,在 macOS 的系统蓝牙设置里面,移除连接的耳机设备,将耳机放入 AirPods 盒子里面,先盖上盒子,然后再打开盒子,此时并按住 AirPods 盒子背后的按钮,直到前面呼吸灯变成白色,然后再 macOS 的蓝牙里面找到新的设备,并连接配对。同时也可参考官方指引步骤 [连接并使用 AirPods 和 AirPods Pro](https://support.apple.com/zh-cn/HT207010) + +### 单耳工作 + +换一个连接设备,检查耳机是否正常,如果是正常,那说明耳机没有问题,问题就出在 macOS 声音管理上面,打开`系统设置` -> `声音` -> `输出模式` ->`设置为居中的平衡模式(既双耳工作)` + +![airpods-settings](https://res.cloudinary.com/incoder/image/upload/v1605689283/blog/airpods-single.png) \ No newline at end of file diff --git a/source/_posts/memory-hz1.md b/source/_posts/memory-hz1.md new file mode 100644 index 000000000..6890eeaff --- /dev/null +++ b/source/_posts/memory-hz1.md @@ -0,0 +1,22 @@ +--- +title: 品·杭州 +date: 2018-04-29 00:11:01 +categories: [Memory] +tag: 杭州 +--- + +上有天堂,下游苏杭,杭州,一个温文尔雅,一个记忆中天堂,一个南方姑娘的城市。 +杭州:毕业后的第二个城市,很开心在这样的城市生活,工作,结识这里的人,杭州和家乡的气候非常相似,因此在杭州有种在家的感觉,在这里遇到的的人,我都会记着你们美丽帅气的脸庞 + + + +18年是一个动荡的一年,曾经的伙伴渐渐的离开了的团队,这两年中,有的人毕业,有的人结婚,有的人生子,有的人成长,感谢我能成为你们生命中的一个过客,和你们一起经历生活百态 + +不管你们在何方,从事着什么样的工作,过着什么样的生活,我会想你们,愿你们的一切顺利 + +粗略的剪影,请异步[优酷](http://v.youku.com/v_show/id_XMzU4NTgyMDE0NA==.html?spm=a2hzp.8244740.0.0) + +{% note default %} +不遵守规则的人,我们叫他废物,但是,不珍惜同伴的人,连废物都不如 +——宁智波·带土 +{% endnote %} \ No newline at end of file diff --git a/source/_posts/microservices-alibaba1.md b/source/_posts/microservices-alibaba1.md new file mode 100644 index 000000000..4e60a8049 --- /dev/null +++ b/source/_posts/microservices-alibaba1.md @@ -0,0 +1,201 @@ +--- +title: 微服务架构 - Alibaba 生态整合(一) +date: 2020-11-11 07:10:00 +categories: Microservices +tag: [Microservices, Alibaba] +--- + +曾几何时,市面上对于微服务,分两个派系,一个派系以阿里为主的 Dubbo 生态体系,还有一派以 Spring Cloud 生态为主的体系,这两个系列的讨论也一直没有停息过。但现在 Spring Cloud Alibaba 的出现,提供了一整套构建分布式应用开发的微服务组件,由于这些组件是构建在原生的 Spring Cloud 之上,因此其服务治理方面的能力可认为是 Spring Cloud Plus, 不仅完全覆盖 Spring Cloud 原生特性,而且提供更为稳定和成熟的实现。那么从本系列就开始跟着我一起用阿里系的应用搭建分布式微服务应用,满足企业级的应用需要,而不是停留在 Dome 级别的应用框架使用。废话不多说,我们一起开始这一系列的实践 + + + +本篇文章主要讲一讲在构建分布式微服务应用时,经常遇到的问题以及对于同类型组件选择,以及在开发过程中相关问题的思考,对于在整个应用开发过程中,开发人员应该怎么去配合等等,那第一个问题是面对我们的业务场景该如何去做技术选型,我们先看 Spring 官方经典的微服务架构图 + +![](https://spring.io/images/diagram-microservices-88e01c7d34c688cb49556435c130d352.svg) + +微服务的核心组件由:网关,服务注册发现,服务配置,熔断限流等组成 + +{% note info %} +注意这里微服务主要以 `Alibaba` 系相关的开源组件为基础构建,并非是 [`Spring Cloud Alibaba`](https://github.com/alibaba/spring-cloud-alibaba) 项目的照搬,而是基于企业实际业务需求的抽象整合,只为提高效率、总结编程套路以及提升编程思想 +{% endnote %} + +## 选型 + +- 编程语言:[Oracle JDK 8](https://docs.oracle.com/javase/8/) +- 构建工具:[Gradle](https://gradle.org) +- 网关路由:[Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) +- 服务通信:[Dubbo](https://dubbo.apache.org/zh) +- 消息管理:[RockerMQ](http://rocketmq.apache.org) +- 分布式事务:[Seata](http://seata.io/zh-cn) +- 注册中心及配置中心:[Nacos](https://nacos.io/zh-cn) +- 限流,熔断,降级:[Sentinel](https://sentinelguard.io/zh-cn) +- 文档管理:[SpringFox](http://springfox.github.io/springfox) + [Knife4j](https://doc.xiaominfo.com) + [Dubbo-Api-Docs](https://dubbo.apache.org/zh/blog/2020/12/22/dubbo-api-docs-apache-dubbo%E6%96%87%E6%A1%A3%E5%B1%95%E7%A4%BA%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/) +- 部署发布:[Docker](https://www.docker.com) + [Nexus Repository OSS](https://www.sonatype.com/nexus/repository-oss) +- 运维监控:[Prometheus](https://prometheus.io) + [Grafana](https://grafana.com) + +![](https://res.cloudinary.com/incoder/image/upload/v1616294894/blog/Spring-Cloud-Alibaba.png) + +## SpringCloud VS SpringCloud Alibaba + +这里我汇总到表格中,方便查看比较 + +![](https://res.cloudinary.com/incoder/image/upload/v1616302368/blog/Spring_Cloud_VS_Spring_Cloud_Alibaba.png) + +## 相关问题 + +### JDK + +#### OpenJDK + +Java 最早由 SUN(Sun Microsystems,发起于美国斯坦福大学,SUN 是 Stanford University Network 的缩写)发明,2006 年 SUN 公司将 Java 开源,此时的 JDK 即为 OpenJDK + +[OpenJDK](http://openjdk.java.net/) 是 Java SE 的开源实现,由 SUN 和 Java 社区提供支持,2009 年 Oracle 收购了 SUN 公司,自此 Java 的维护方之一的 SUN 也就变成了 Oracle + +大多数 JDK 都是在 OpenJDK 的基础上编写实现的,比如 IBM J9,Azul Zulu,Azul Zing 和 Oracle JDK。几乎所有的 JDK 都派生自 OpenJDK,他们之间不同的是授权许可证。常见的 OpenJDK 发行商 + +| **发行商** | **长期支持(TLS)** | **许可证(license)** | **TCK 测试** | **未修改的上游构建** | **提供商业支持** | +|:------------------------:|:-------------:|:----------------:|:--------------:|:------------:|:----------------:| +| [AdoptOpenJDK](https://adoptopenjdk.net/) | Yes | Yes | No | Optional | Optional(IBM) | +| [Alibaba Dragonwell](http://dragonwell-jdk.io/) | Yes | Yes | Yes | No | No | +| [Amazon Corretto](https://aws.amazon.com/corretto/) | Yes | Yes | Yes | No | Optional
(on AWS) | +| [Azul Zulu](https://www.azul.com/downloads/zulu-community/?package=jdk) | Yes | Yes | Yes | No | Optional | +| [BellSoft Liberica JDK](https://bell-sw.com/pages/downloads/) | Yes | Yes | Yes | No | Optional | +| [IBM Java JDK](https://www.ibm.com/support/pages/java-sdk-downloads) | Yes | No | Yes | No | Yes | +| [ojdkbuild](https://github.com/ojdkbuild/ojdkbuild) | Yes | Yes | No | Yes | No | +| [OpenLogic OpenJDK](https://www.openlogic.com/openjdk-downloads) | Yes | Yes | No | No | Optional | +| [Oracle Java SE](https://www.oracle.com/java/technologies/javase-downloads.html) | Yes | No | Yes | No | Yes | +| [Oracle OpenJDK](https://openjdk.java.net/) | No | Yes | Yes | Yes | No | +| [Red Hat OpenJDK](https://developers.redhat.com/products/openjdk/download) | Yes | Yes | Yes | No | Yes | +| [SAP SAPMachine](https://sap.github.io/SapMachine/) | Yes | Yes | Yes | No | No | + +>TLS:long-term support,长期支持(LTS)是一种产品生命周期管理策略,在该策略中,与标准版相比,计算机软件的稳定版本可以维持更长的时间。该术语通常保留给开源软件,它描述的软件版本比该软件的标准版本支持数月或数年的支持。 +>TCK:Technology Compatibility Kit,技术兼容性套件(TCK)是一套测试套件,至少名义上检查Java规范请求(JSR)的特定声称实施是否符合要求 + +#### OralceJDK + +显而易见 OracleJDK 是在 Oracle 收购 SUN 公司之后,基于 OpenJDK 源码构建的 JDK 被命名了 OracleJDK,两则之间没有重大的技术差异 + +#### 两者的区别 + + +#### 问题 + +有人会说了,这有啥好说的,我们在公司开发都是用 OracleJDK 的。曾经我也以为这两个区别不是很大,看公司的使用情况了,直到我使用了 CentOS 7 系统默认带的 OpenJDK 来编译 Gradle 项目,死活是编译不过,总是提醒我找不到 `tools.jar` 包。有图有真相 + +![](https://res.cloudinary.com/incoder/image/upload/v1616514430/blog/gradle-openjre.png) + +一开始,我把以为是环境配置的问题,但是经过一番折腾,卸载了自带的 OpenJDK,然后再用 `yum install java` 命令去安装 OpenJDK,发现并不是环境的问题,而是系统自带的这个 OpenJDK 是 JRE,所以并没有包含 `tools.jar` 文件。所以这个问题就是你系统 JDK 的问题了。建议卸载 JRE,重新安装 JDK + +```bash +# 可以先查找 JDK,下面命令是我查找 java-1.8 的相关应用 +yum search java-1.8 | grep -i --color JDK +# 也可以直接安装 JDK,比如我这里提供的 java-1.8.0-openjdk-devel.x86_64 +yum install java-1.8.0-openjdk-devel.x86_64 +``` + +![](https://res.cloudinary.com/incoder/image/upload/v1616515361/blog/centos-openjdk.png) + +### Gradle or Maven + +关于如何使用 Gradle 构建项目,以及使用 Gradle 配置符合企业敏捷开发需求,可查看我的 [Gradle](https://incoder.org/tags/Gradle) 系列的文章 + +### jar 与 bootJar + +之前在[《SpringBoot(二) 启动分析JarLauncher》](https://incoder.org/2019/07/05/springboot2/)文章中进行对 SpringBoot 应用启动做了分析,提到了 [jar 规范](https://incoder.org/2019/07/05/springboot2/#jar%E8%A7%84%E8%8C%83),做了简单的介绍,那么本篇在此基础上进一步的完善这个知识点 + +> 这里以 [rc-microservices-alibaba](https://github.com/RootCluster/rc-microservices-alibaba) 项目的 `microservices-alibaba-gateway` 模块的编译为例 + +#### jar + +jar(Java Archive)可以看做是特殊文件压缩的一种,通常用于聚合大量的 Java 类文件,相关的元数据和资源文件到一个文件,以便分发 Java 平台应用软件或库。jar 文件是一种归档文件,以 ZIP 格式构建,以 `.jar` 为文件扩展名。包含一个可选的 `META-INF` 目录,可以通过命令行 jar 工具或使用 Java 平台中的 `java.util.jar` API 创建 jar 文件 + +可以看到,我们打包成 jar 的文件,仅仅是源码+资源文件,以及生成的 `META_INF` 文件 + +```text +microservices-alibaba-gateway-1.0-SNAPSHOT + ├── META-INF + ├── org + │ └── incoder + │ └── gateway + │ ├── config + │ ├── exception + │ └── filter + ├── static + └── templates +``` + +#### bootJar + +看名字就知道,这是 SpringBoot 的专属 jar。为什么会有这种 jar,原因是在 SpringBoot 出现之前,我们的 jar 应用想要运行,需要将应用放入到 Tomcat 中。而 SpringBoot 的出现改变了这层关系,是 SpringBoot 在打包成 bootJar 时,会内置 Tomcat,我们可以直接运行启动 jar 应用,可能有人会说,这怎么改变了,不都还是运行在 Tomcat 上么。没错它确实依然运行在 Tomcat 上,但是他们的加载方式改变了 + +我们可以看到,打成 bootJar 的文件,除了 `META-INF` 相关文件,并且包含了 `BOOT-INF` 的 lib 路径下存放项目所使用的所有第三方的 jar 包 ,同时在打包的根目录,生成了 SpringBoot 的 loader 相关的文件 + +```text +microservices-alibaba-gateway-1.0-SNAPSHOT + ├── BOOT-INF + │ ├── classes + │ │ ├── META-INF + │ │ ├── org + │ │ │ └── incoder + │ │ │ └── gateway + │ │ │ ├── config + │ │ │ ├── exception + │ │ │ └── filter + │ │ ├── static + │ │ └── templates + │ └── lib + ├── META-INF + └── org + └── springframework + └── boot + └── loader + ├── archive + ├── data + ├── jar + ├── jarmode + └── util +``` + +### Spring 生态 + +1. Spring:一个一站式轻量级Java 开发框架,核心是控制反转(IOC)和面向切面(AOP),针对开发 Web 层,业务层,持久层等提供了多种配置解决方案,也是整个微服务开发的基石 +2. SpringMVC:是 Spring 基础之上的一个 MVC 框架,主要处理 Web 开发的路径映射和视图渲染,属于 Spring 框架中 Web 层开发的一部分(开发配置非常繁琐,复杂) +3. SpringBoot:专注于服务方面的接口开发,和前端解耦,默认优于配置,一定程度上取消了 XML 配置,是一套快速开发的脚手架,能快速开发单个微服务 +4. SpringCloud:大部分功能组件基于 SpringBoot 去实现,提供了完整的微服务架构的技术生态,SpringCloud 专注于微服务的整合和管理 + +### 单工程 or 聚合工程 + +>个人推荐单工程的方式,毕竟聚合工程最终会随着业务的发展推进,需要拆分为单项目开发管理,那还不如一开始就拆分 + +#### 单工程 + +这里的单工程是指,每一个模块都是一个项目,由一个仓库进行管理,特点及要求如下 + +1. 适合团队小组分工明确,开发人员多 +2. 适合项目迭代快 +3. 需要比较健全的基础设施,比如网关,公共基础工具包,消息管理,以及自动化部署相关服务设施 + +>相关的构架过程可参考 [Gradle(三)SpringBoot 单工程](https://incoder.org/2020/12/16/gradle3/) 文章 + +#### 聚合工程 + +这里的聚合工程是指,将整个系统开发的所有模块以及公共模块都放在一个项目工程中,也就是用同一个仓库来进行管理,特点如下 + +1. 适合项目初期,项目分工不是特别明确,开发人员少 +2. 项目需要集中管理 + +>相关的构架过程可参考 [Gradle(四)SpringBoot 聚合工程](https://incoder.org/2021/03/06/gradle4/) 文章 + +### 业务拆分 + +对于服务的拆分是没有统一的标准,除了通过实际的业务场景,团队能力,人员组织架构等多种因素综合考虑。都根据实际的需求进行调整,对于拆分主要从以下原则去思考 + +1. 单一职责原则:保证每个服务只做好一件事,体现“高内聚,低耦合”,尽量减少对外界环境的依赖 +2. 服务依赖原则:避免服务间的循环依赖,在设计时就需要对服务进行分级,区分核心服务与非核心服务 +3. Two Pizza Team原则:让团队保持在2 个披萨就能让队员吃饱的小规模概念 + +## 参考 + +* [传统行业转型微服务的挖坑与填坑](https://zhuanlan.zhihu.com/p/57385234) +* [Java官方(Oracle/Sun)发布的JDK,和开源项目OpenJDK,里面包含的JVM是否相同](https://www.zhihu.com/question/19882320) +* [OpenJDK和Oracle JDK有什么区别和联系?](https://cloud.tencent.com/developer/article/1598291) \ No newline at end of file diff --git a/source/_posts/microservices-springcloud1.md b/source/_posts/microservices-springcloud1.md new file mode 100644 index 000000000..a8fa4e333 --- /dev/null +++ b/source/_posts/microservices-springcloud1.md @@ -0,0 +1,34 @@ +--- +title: 微服务架构 - SpringCloud 生态整合(一) +date: 2020-11-08 07:10:00 +categories: Microservices +tag: [Microservices, SpringCloud] +--- + +微服务这一概念在 2014 年的 3 月份随着 James Lewis 和 Martin Fowler 在博客中对于微服务这一概念做出了详细的阐述,开始走进开发者的视野。在 Spring 官方的加持下,助推微服务架构风格的应用开始火边整个后端领域。在早起微服务化的演进中 Netflix 的一些列开源的组件,迅速占领微服务生态中的 C 位,提供了网关路由,负载均衡,服务注册发现,服务通信,服务熔断限流等核心组件 + + + +本系列文章是基于 Spring 官方提供的组件,完成相关功能组件的整合,以满足企业需求为目的的学习总结。本篇文章先从 Spring 官网经典的微服务架构图开始 + +![](https://spring.io/images/diagram-microservices-88e01c7d34c688cb49556435c130d352.svg) + +{% note info %} +注意这里主要以 `Spring` 系官方相关的开源组件为基础构建,并非是 [`Spring Cloud`](https://spring.io/projects/spring-cloud) 项目的照搬,而是基于企业实际业务需求的抽象整合,只为提高效率、总结编程套路以及提升编程思想 +{% endnote %} + +当然这只是我在实际生产实践中的总结,并不一定适合你的业务场景,你也可以参考我的另一个系列 [Alibaba 生态整合](https://incoder.org/2020/11/11/microservices-alibaba1/),希望能对你有所帮助 + +## 选型 + +- 编程语言:[Oracle JDK 8](https://docs.oracle.com/javase/8/) +- 构建工具:[Gradle](https://gradle.org) +- 网关路由:[Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) +- 服务通信:[OpenFeign](https://github.com/OpenFeign/feign) +- 注册中心:[Eureka](https://github.com/Netflix/eureka) +- 配置中心:[Spring Cloud Config](https://github.com/spring-cloud/spring-cloud-config) +- 限流,熔断,降级:[Hystrix](https://github.com/Netflix/Hystrix) +- 文档管理:[SpringFox](http://springfox.github.io/springfox) + [Knife4j](https://doc.xiaominfo.com) +- 部署发布:[Docker](https://www.docker.com) + [Nexus Repository OSS](https://www.sonatype.com/nexus/repository-oss) +- 链路追踪:[Spring Cloud Sleuth](https://cloud.spring.io/spring-cloud-sleuth) + [Zipkin](https://zipkin.io) + diff --git a/source/_posts/microservices.md b/source/_posts/microservices.md new file mode 100644 index 000000000..8b442c6fa --- /dev/null +++ b/source/_posts/microservices.md @@ -0,0 +1,361 @@ +--- +title: 【译】• 微服务 +date: 2019-06-01 10:00:00 +categories: + - [Translation] + - [SpringBoot] +tag: [SpringBoot] +--- + +这是第一篇翻译文章,用于学习近些年火热的微服务,这篇是`微服务`概念是由 `James Lewis` 所著,虽然官网已有[中文翻译](https://mp.weixin.qq.com/s/clbRQZ6-5YoX68MzwBfQ_Q),但是在学习过程中,应该应该动手输出,这样有助于对知识的理解和记忆,废话不多说,开始翻译 + +## 微服务 +> 近些年术语“微服务架构”就像雨后春笋般蓬勃的发展,微服务描述软件应用设计是独立可部署服务一个特殊方式。虽然这些都不够准确的去定义一个架构风格,但存在一些通用的特质(大家达成共识的特征),如何去组织围绕业务能力,如何自动化部署,端点的智能发现,以及语言和数据去中心化的控制 + + + +### James Lewis +James Lewis 是 ThoughtWorks 的首席顾问,也是技术顾问委员会的成员。James 利用小型协作服务构建应用程序的兴趣起源于大规模集成企业系统的背景。他构建数量级的系统都使用微服务,并且几年来,他一直积极参与不断地社区发展 + +### Martin Fowler +Martin Fowler 是一个作者,演讲家,和普通的软件开发,他一直对如何组件化软件系统的问题感到困惑,他希望微服务能够实现其倡导者所发现的早期承诺 + +---------- + +“微服务”在当时任然是一个新的名词。虽然我们的自然倾向是通过这些构建,这个技术分隔软件系统,这个术语描述了一种我们发现越来越有吸引力的软件系统风格。我们已经看到很多项目在过去的几年中使用这种风格,到目前为止的结果是积极的,以至于对于我们的许多同事而言,这已成为构建企业级应用的默认样式。然而,遗憾的是,没有太多信息可以描述微服务的风格以及微服务是如何实现 + +简而言之,**微服务架构风格**[^1]是一个开发单应用作为小型服务套件开发模式,每个应用运行在自己的进程中并且他们之间通过轻量级的机制进行通信,通常的如 HTTP 资源 API。这些服务围绕业务能力并且这些都是可以独立的自动化部署。这些服务我们进行**去中心化**的集中管理。这些服务可以使用不同的编程语言来编写,同样也可以使用不同的数据存储技术 + +开始解释微服务风格,将它与单体风格进行比较是有用的:作为单元构建的单片应用程序。企业应用程序通常由 3 个之上主要构成部分: +* 客户端(由用户机器上的浏览器中运行的 HTML 页面和 JavaScript 组成) +* 数据库(由插入到公共中的许多表组成,通常是关系型,数据库管理系统) +* 服务端应用程序。 +这个服务端应用程序处理 HTTP 请求,执行域逻辑,从数据库中检索和更新数据,并选择装配发送到浏览器的 HTML 视图。这个服务端应用程序是一个单体的,一个逻辑可执行文件[^2] ,任何一次更改都生成一个新的版本去构建和部署 + +这种单体服务是构建这种系统的自然方式。所有的请求逻辑处理都运行在一个进程中,允许你使用语言的基本特性将应用划分为类,功能和命名空间。对于一些其他样例,你可以运行和测试应用在开发者的笔记本上,并使用部署管道确保已正确测试并部署到生成环境中。你可以通过负载均衡运行许多实例来进行水平扩展(常见单体应用模式 前面通过Nginx负载均衡,在 Nginx 后面运行多个应用实例) + +单体应用程序可以成功,但是越来越多的人感到沮丧-特别是随着更多应用程序部署到云端。更改周期紧密相连-对应用程序的一小部分更改,需要重新构建和部署整个应用。随着时间的推移,通常很难保持良好的模块化结构,是的更难以保持改变只影响模块中的一个模块的更改。扩展需要扩展整个应用程序,而不是需要更多资源的部分扩展 + +![图 1:单体应用和微服务](https://martinfowler.com/articles/microservices/images/sketch.png) +从上图可知单体应用和微服务在部署的角度(可升缩角度)来讲: +* 单体应用:进行可升缩,是将单体应用整个进行升缩,每台机器上的应用都是相同的 +* 微服务:每个服务都是独立的单元,可根据需要对服务单元进行任意组合进行升缩,每台机器上的应用是不相同的 + +这些挫折导致了微服务架构的风格:构建应用程序作为服务套件。事实上服务是独立部署和可扩展的,每个服务之间也提供坚实模块的边界,甚至允许不同的编程语言编写不同的服务。它们也可以由不同的团队来管理 + +我们并不认为微服务风格是新颖的或创新的,其根源可以归结为 Unix 的设计原则。但是我们认为这些没有足够的人考虑微服务架构风格,如果使用它们,许多软件的开发会更好,从中获益匪浅 + +## 微服务架构的特征 + +我们不能说微服务架构风格有正式的定义,但我们可以尝试描述我们认为合适标签的架构的共同特性。与概述共同特征的任何定义一样,并非所有的微服务架构都具备所有的特征,但我们确实希望大多数微服务架构都具有大多数的特性。虽然我们的作者一直是这个相当宽松社区的积极成员,我们的目的是尝试描述我们在自己的工作中所看到的以及我们所知道的团队的努力,特别是我们没有规定一些符合的定义 + +### 服务组件化 + +只要我们参与软件行业,人们一直希望通过将组件集成在一起来构建系统,我们在物质世界中看待事物的方式有很多类似,在过去的几十年中,我们已经看到了大多数语言平台的大型公共 libraries 的大量进展 + +在谈论组件时,我们遇到了组件构成的困难定义,我们的定义**组件**是一个可独立更换和升级的软件单元 + +微服务架构会使用到这些 libraries,但他们讲自己的软件组件化的主要方式是分解为服务。我们定义 **libraries** 作为组件链接到程序中,也可以使用内存函数中调用的组件,而**服务**是进程外的组件,它们与诸如 Web 服务请求或远程调用之类的机制进行通信。(这与许多 OOP[^3] 中的服务对象感念不同) +>所谓的库都是调用在同一个进程当中,而服务的调用是跨进程的,要通过 Web 请求的方式或者是 RPC 的方式进行通信 + +将服务用作组件(而不是 libraries)的一个主要原因是服务可以独立部署。如果你在单个进程中有多个 libraries组成的应用程序[^4],则对任何单个组件的更改都会导致必须重新部署整个应用程序。但如果一个应用由多个服务组成,你可以期望任何单服务的改变仅需要更新自己。这不是绝对的,一些更改改变了部分服务接口,从而导致一定的协调,但良好的微服务架构的目标是通过服务合同中的内聚服务边界和演化机制来最小化这些架构 + +将服务用作组件的另一个结果是更明确的组件接口,多数语言没有很好的机制来定义已[发布的接口](https://martinfowler.com/bliki/PublishedInterface.html)。通常这并不仅仅只有文档和原则性问题,来防止客户破坏组件的封装原则,而且会导致组件间过度紧密耦合。通过使用显示远程调用机制,服务可以更容易地避免这种情况 + +使用这种服务也有一些缺点。远程调用通常要比进程内调用成本要高,因此远程调用需要更粗粒度的,这通常更难以使用。如果你需要去更改组件间的职责分配,那么当你跨越流程边界时,这种行为的变化就更难 + +在第一次中,我们可以观察到服务可以映射到运行时的进程,但这只是一个大致的描述。一个服务可能包含多个进程,这些进程始终一起开发和部署,这样的应用进程和数据库是这个服务所独有的 + +### 围绕业务能力进行组织 + +在寻找将大型应用程序拆分为多个部分时,通常管理侧重于技术层,导致 UI 团队,服务器逻辑团队和数据库团队。当团队按照这些方式分开时,即使是简单的更改也可能导致跨团队项目需要时间和预算批准。一个聪明的团队围绕这个进行优化,并未减少这两个情况的发生——会强制将逻辑放置到可以访问的应用中。换句话说,逻辑无处不在。这就是康威定律[^5] 的一个例子 +>Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure +> ——Melvyn Conway, 1967 + +![Conway's Law in action](https://martinfowler.com/articles/microservices/images/conways-law.png) +微服务划分方法是不同的,分为围绕**业务能力**组织的服务。此类服务为该业务领域采用广泛的软件实现,包括用户页面,持久存储,以及任何额外协作。因此,团队是跨职能的,包括开发所需的全部技能:用户体验,数据库和项目管理 + +![Service boundaries reinforced by team boundaries](https://martinfowler.com/articles/microservices/images/PreferFunctionalStaffOrganization.png) + +>微服务有多大? +虽然“微服务”已经成为这种架构风格的流行名称,但它的名字确实导致了对微服务的不关注以及关于什么构成“micro”的争论。在我们与微服务从业者的对话中,我们看到了一系列服务规模。报道的最大数量遵循亚马逊的 Two Pizza Team 的概念(比如:整个团队都可以讨厌两个披萨),意味着不超过十二人。对于规模较小的服务,我们已经看到一个6人的团队在支持6个服务。 +> +> 这导致了这样的问题:在这个尺寸范围内是否存在足够大的差异,每个人的服务和每个服务的尺寸不应该集中在一个微服务的标签下。目前我们认为将它们组合在一起会更好,但当我们进一步探索这种风格时,我们肯定会改变主意 + +以这种方式组建的一家公司是 [www.comparethemarket.com](http://www.comparethemarket.com)。跨职能团队负责构建和运营每个产品,每个产品分为多个通过消息总线进行通信的单独服务。 + +大型单机应用程序也可以围绕业务功能进行模块化,尽管这不是常见的情况。当然,我们会敦促一个庞大的团队构建一个单体的应用,以便在业务线上划分自己。我们在这里看到的主要问题是,它们往往围绕太多的背景进行组织。如果整体跨许多这些模块化边界,那么团队中的个体成员很难将它们适应其短期组织中。此外,我们看到模块化生产线需要大量的规范来执行。服务组件所需要的更明确的分离是的更容易保持团队边界清晰 + +### 产品不是项目 + +我们看到的大多数应用程序开发工作都使用项目模型:其目的是提供一些软件然后被认为是完成的。完成后,软件将移交给维护组织,构建他的项目团队将被解散。 + +微服务支持者倾向于避免这种模式,而是倾向于认为团队应该在其整个生命周期内拥有产品。对此的一个共同启示是亚马逊的概念[“你构建,运行它”](https://queue.acm.org/detail.cfm?id=1142065),开发团队对生产中的软件负全部责任。这使的开发人员能够日常接触他们的软件在生成中的行为,并增加与用户的联系,因此他们必须承担至少一些支持工作。 + +产品心态,与业务能力的联系紧密相连。不是将软件视为一组要完成的功能,而是存在一种持续的关系,其中的问题是软件如何帮助其用户增强业务能力 + +没有理由不采用单一应用程序采用相同的方法,但较小的服务粒度可以更容易地在服务开发人员和用户之间创建个人关系 + +### 智能端点和哑的 pips + +在构建不同进程间通信结构时,我们已经看到许多产品和方法都强调将重要的`smarts`放入沟通机制本身。一个很好的例子是企业服务总线(ESB),其中 ESB 产品通常包括用于消息路由,编排,转换和应用业务规则的复杂工具。 + +>微服务和 SOA +>当我们谈到微服务时,一个常见的问题,这是否是我们十年前看到的面向服务的体系结构(SOA),这一点是有道理的,因为微服务风格非常类似于 SOA 的一些拥护者所支持的。然而,问题在于 SOA 意味着[太多不同的东西](https://martinfowler.com/bliki/ServiceOrientedAmbiguity.html),并且大多数时候我们遇到称为“SOA”的东西,它与我们在这里描述的样式有很大不同,通常是由于专注于用于集成单片应用程序的 ESB +> +>特别是我们已经看到了许多拙劣的服务导向实现——从隐藏 ESB[^6] 中的复杂性的趋势,失败的多年计划,耗资数百万美元,没有任何价值,积极治理模式,积极抑制变化,有时很难看到过去的这些问题 +> +>当然,微服务社区中使用的许多技术都是从开发人员在大型组织中集成服务的经验中发展而来的。[容忍读者](https://martinfowler.com/bliki/TolerantReader.html)模式就是一个例子。使用网络努力做出了贡献,使用简单的协议是从这些经验中得到的另一种方法——远离中心标准的反应,这种标准已达到复杂性,坦率地说,[令人叹为观止](http://wiki.apache.org/ws/WebServiceSpecifications)(只要你需要一个本体来管理你的本体,你就知道你遇到了很大的麻烦) +> +>SOA 的这种场景表现导致一些服务提倡者完全拒绝 SOA 标签,尽管其他人认为服务是 SOA 的一种形式,也许正确的服务向导,无论哪种方式,SOA[^7] 意味着这些不同的事物意味着有一个更清晰地定义这种建筑风格的术语是有价值的 + +微服务社区倾向于采用另一种方法:智能端点和愚蠢的 pips。从微服务构建的应用程序旨在尽可能地分离和聚集——他们拥有自己的域逻辑,在经典的 Unix 意义上更像是过滤器——接收请求,适当地应用逻辑并产生响应。这些是使用简单的 RESTish 协议而不是复杂的协议(如 WS-Choregoraphy 或 BPEL 中央工具的编排)编排的。 + +最常用的两个协议是 HTTP 请求——响应资源 API 和轻量级消息[^8] 传递。第一个最好的表达方式是 +>Be of the web, not behind the wed +> -- lan Robinson + +微服务团队使用万维网(在很大程度上,Unix)构建的原则和协议。经常使用的资源可以通过开发人员或操作人员的非常小的努力来缓存。 + +常用的第二种方法是通过轻量级消息总线进行消息传递。选择的基础设施通常是哑的(哑仅作为消息路由器的行为)—— 向 RabbitMQ 或者 ZeroMQ 这样的简单实现不仅仅提供可靠的异步结构——智能功能存在于那些生产和消费诸多消息的各个端点中,即存在于各个服务中。 + +在一个单体应用中,组件在进程中执行,它们之间通信是通过方法调用或函数调用。将整体变为微服务的最大问题在于改变通信模式。从内存中方法调用到 RPC 的简单转换导致繁琐的通信,这种通信效果不佳。相反,您需要粗粒度的方法替换细粒度的通信。 + +### 去中心化的治理 + +集中治理的后果之一是在单个技术平台上实现标准化的趋势。经验表明,这种方法是有限的——不是每个平台是一样的,不是每个平台的解决方案是一致的。我们推荐使用正确的工具来完成工作,而单体应用程序在一定程度上利用不同的语言,但这并不常见 + +将单个应用组件拆分为多个服务,我们可以在构建每个组件时做出选择。你希望使用 Node.js 建立一个简单的报告页面?没问题。想通过 C++ 来实现出彩的实时组件?没毛病。想换不同风格的数据库,以更好地适应一个组件的读取行为?可以重建 + +当然,只是因为你可以做某件事,并不意味着你可以应该——但以这种方式划分你的系统意味着你可以选择 + +构建微服务的团队也更喜欢采用不同的标准方法。他们更倾向于其他开发人员可以使用的有用工具来解决与他们面临的类似问题,而不是使用在纸上某处写下的一组定义标准。这些工具通常从实现中收集并广泛的共享,有时,但不仅仅是使用内部开源模型。现在 Git 和 GitHub 已经成为事实上的版本控制系统,开源实践在内部变得越来越普遍。 + +Netflix 是遵循这一理念的组织的一个很好的例子。共享有用的,尤其是经过实战考验的代码,因为鼓励其他开发人员以类似的方式解决类似问题,但如果需要,可以选择不同的方法。共享库往往侧重于数据存储,进程间通信的常见问题,我们将在下面进一步讨论基础架构自动化 + +对于微服务社区来说,管理费用特别缺乏吸引力。这并不是说社区不重视服务契约。恰恰相反,因为往往会有更多。只是他们正在寻找管理这些契约的不同方式。像[容错读取](https://martinfowler.com/bliki/TolerantReader.html)和[消费者驱动的契约](https://martinfowler.com/articles/consumerDrivenContracts.html)这样的模式通常应用于微服务。这些援助服务契约独立发展。在构建过程中执行消费者驱动的契约可以增强信心,并提供有关您的服务是否正常运行的快速反馈。事实上,我们知道澳大利亚的一个团队通过消费者驱动的契约推动服务的建设。他们使用简单的工具来定义合同服务。在编写新服务的代码之前,这将成为自动构建的一部分。然后,该服务仅构建在满足合同的程度——在构建新软件时避免“YAGNI”[^9] 困境的优雅方法。这些技术和围绕他们成长的工具通过减少服务间的时间耦合来限制重要合同管理的需要 + +>多语言,多选择 +JVM 作为平台的发展只是在一个通用平台中混合语言的最新例子。近十年依赖,通常的做法是采用更高级别的语言来更高级别的抽象。同样,在平台底层以更低层次的编程语言编写性能敏感的代码也很普遍。然而,许多单块系统并不需要这种级别的性能优化,另外 DSL 和更高层次的抽象也不常用(这令我们感到失望)。相反,许多单体应用通常就使用单一编程语言,并且对所用的技术数量进行限制的趋势[^10] + +也许去中心化治理的最高点就是建立它/运行它,由亚马逊推广的精神。团队负责他们构建的软件的所有方面,包括全天候运行软件。这种责任水平的下放绝对不是常态,但我们确实看到越来越多的公司将责任推向开发团队。Netflix 是另一个采用这种精神[^11] 的组织。每天晚上凌晨 3 点您被你的寻呼机唤醒,无疑是在编写代码时专注于质量的强大动力。这些想法与传统的集中治理模式相差甚远 + +### 去中心化数据管理 + +去中心化数据管理以多种不同的方式呈现。在最抽象的层面上,它意味着世界的概念模型在不同系统之间会有所不同。在整个大型企业时,这是一个常见问题,客户的销售视角将与支持视角不同。从销售视角中称为“客户”的某些内容,可能根本不会出现在支持视角中。那些在两个视角中具有相同属性的事物,或许在语义上有微妙的不同 + +>经过实战检验的标准和强制执行的标准 +微服务团队倾向于避开企业架构小组制定的严格执行标准,但很乐意使用甚至宣传 HTTP,ATOM 和其他微格式等开放标准的使用,这有点很二分法 +> +>关键的区别在于如何制定标准以及如何实施标准。有 IETF 等团体管理的标准只有在更广泛的世界中有多高实施时才能成为标准,并且通常来至于成功的开源项目 +> +> 这些标准与企业的许多标准不同,后者通常由最近没有编程或受供应商过度影响的团体开发 + +此问题在应用程序间很常见,但也可能在应用程序中发生,特别是将应用程序划分为单独的组件时。一种有用的思考方式是“领域驱动设计”中的[“限定上下文”](https://martinfowler.com/bliki/BoundedContext.html)的概念。DDD 将复杂领域划分为多个限界上下文,并映射出他们之间的关系。此过程对单体和微服务架构两者都很有用,而且就像前面有关“业务功能”一节中所讨论的那样,在服务和各个限界上下文之间所存在的自然的联动关系,能有助于澄清和强化这种划分。 + +除了关于概念模型的分散决策之外,微服务还分散了数据存储决策。虽然单一应用程序更喜欢使用单个逻辑数据库来存储持久性数据,但企业通常更喜欢在一系列应用程序中使用单个数据库——其中许多决策是通过供应商的商业模式来实现。微服务更喜欢让每个服务管理自己的数据库,可以是同一数据库技术的不同实例,也可以是完全不同的数据库系统——这种方法称为["Polyglot Persistence"](https://martinfowler.com/bliki/PolyglotPersistence.html)。你可以在整体中使用多语言持久性,但它在微服务中更常出现。 +![](https://martinfowler.com/articles/microservices/images/decentralised-data.png) + +跨服务分散数据责任对管理更新有影响。处理更新的常用方法是在更新多个资源时使用事务来保证一致性。这种方法通常用于整体结构中。 + +使用这样的事务有助于保持一致性,但会产生显著的时间耦合,在多个服务中是有问题。众所周知,分布式事务很难实现,因此微服务架构[强调服务之间的无事务协调](http://www.eaipatterns.com/ramblings/18_starbucks.html),明确承认一致性可能只有最终的一致性,而问题则通过补偿操作来处理。 + +选择以这种方式管理不一致是许多开发团队面临的新挑战,但它通常与业务实践相匹配。企业通常会处理一定程度的不一致,以便快速响应需求,同时采取某种逆转流程来应对错误。只要修复错误的成本低于在更大的一致性下丢失业务的成本,那么权衡是值得的。 + +### 基建设施自动化 + +基础设施自动糊技术在过去几年中发生了巨大变化——特别是云和 AWS 的发展降低了构建,部署和运行微服务的操作复杂性。 + +许多使用微服务构建的产品或系统都是由具有丰富的[持续交付(Continuous Delivery)](https://martinfowler.com/bliki/ContinuousDelivery.html)经验的团队构建的,并且是前身的[持续集成(Continuous Integration)](https://martinfowler.com/articles/continuousIntegration.html)。以这种方式构建软件的团队广泛使用基础设施自动化技术。这在下面显示的构建管道中说明 + +![basic build pipeline](https://martinfowler.com/articles/microservices/images/basic-pipeline.png) + +由于这不是关于持续交付的文章,我们将在这里引起注意几个关键功能。我们希望尽可能地信心使我们的软件正常工作,因此我们进行了大量的**自动化测试**。推广工作软件“向上”管道意味着我们**自动化部署**到每个新环境。 + +>做正取的事情很容易 +我们发现由于持续交付和部署而增加自动化的一个副作用是创建有用的工具来帮助开发人员和操作人员。用于创建人工制品,管理代码库,提供简单服务或添加标准监视器和日志记录的工具现在非常普遍。网上最好的例子可能是 [Netflix 的开源工具集](https://netflix.github.io/),但还有其他一些,包括我们官方使用的 [Dropwizard](http://dropwizard.codahale.com/) + +一个单一的应用程序将非常愉快地构建,测试和推动通过这些环境。事实证明,一旦你投资自动化整个生产的生产之路,那么部署更多地应用程序视乎不再那么可怕。请记住,CD的目标之一就是使用部署无聊,所以无论是一个还是多个应用,只要它任然无聊就无聊无所谓[^12] + +我们看到团队使用广泛的基础设施自动化的另一个领域是管理生产中的微服务。与我们上面的断言相反,只要部署很无聊,单块和微服务之间没有太大的区别,每个部署的运营环境可能会截然不同 +![Module deployment often differs](https://martinfowler.com/articles/microservices/images/micro-deployment.png) + +### 容错设计 +使用服务作为组件的结果是,应用需要设计以便他们能够容忍服务的失败。由于提供者不可用(不可达)等,任何服务调用都可能失败,客户端必须尽可能优雅地对此作出响应。与单体设计相比,这是一个缺点,因为它引入了额外的复杂性来处理它。结果是微服务团队持续不断反思服务失败如何影响用户体验。Netflix 的 Simian Army 在工作日引发服务甚至数据中心的故障,以测试应用程序的弹性和监控。 + +>断路器和“可随时上线的代码” +断路器一词与其他一些模式一起出现发布,如 Bulkhead 和 Timeout。在构建彼此通信的应用系统时,将这些模式加以综合运用变得至关重要。[Netflix 博客](http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html)这篇文章很好的解释了这些模式如何应用。 + +这种在生产环境中所进行自动化测试,足以让大多数运维组织兴奋地浑身颤栗,就像在一周的长假即将到来前那样。这并不是说单体式架构风格不具备复杂的监控设置——在我们的经验中,这在单体系统中并不常见罢了。 + +由于服务可能随时发生故障,因此能够快速检测故障并在可能情况下自动恢复服务非常重要。微服务应用程序非常重视应用程序的实时监控,检查“架构元素指标”(数据库每秒获得多少请求)和“业务相关指标”(例如每分钟收到多少订单)。当系统某个地方出现问题,语义监控可以提供一个预警,从而触发开发团队跟进和调查工作。 + +这对于微服务架构尤为重要,因为微服务对编排和事件协作的偏好会导致紧急行为。虽然许多权威人士赞扬偶然出现的价值,但事实是,新兴行为有时可能是一件坏事。监控对于快速发现下不良紧急行为至关重要,因此可以修复。 + +>“同步调用”有害 +每当您在服务之间进行多次同步调用时,您将遇到停机的乘法效应。简而言之,就是系统停机时间成为各个组件停机时间的产物。您面临一个选择,使您的呼叫异步或管理停机时间。在 `www.guardian.co.uk`网站上,他们在新平台上实施了一条简单的规则——每个用户请求一次同步调用,而在 Netflix,他们的平台 API 重新设计已经在 API 结构中建立了异步性。 + +monoliths 可以像微服务一样透明——事实上,他们应该是。不同之处在于您绝对需要知道在不同进程中运行的服务何时断开连接。对于同一过程中的库,这种透明性不太可能有用。 + +微服务团队希望看到针对每个服务的复杂监控和日志记录设置,例如显示上/下状态的仪表板以及各种运营和业务相关指标。有关断路器状态,当前吞吐量和延迟的详细信息是我们经常遇到的其他示例。 + +### “演进式”设计 + +微服务从业者通常拥有“演进式”设计背景,而且通常将服务分解视为额外的工具,使应用程序开发人员能够控制应用程序中的更改而不会减慢变更。变更控制并不一定意味着改变——通过正确的态度和工具,你可以对软件进行频繁,快速和良好控制的变更。 + +每当你尝试将软件系统分解为组件时,就面临着如何进行划分各个部分的决定——我们决定将应用程序切分的遵循的原则是什么?组件的关键属性是独立替换和可升级性[^13] 的特点——这意味着需要寻找这些点,即想象在不影响其合作者的情况下重写组件。事实上,许多微服务团队通过明确预期服务将来会废弃,而不是守着这些服务做长期的演进。 + +Guardian 网站是一个设计和构建为单体的应用程序的一个很好例子,然而它已经开始向在微服务方向进行演进了。原先的单体系统依然是网站的核心,但在添加新特性时他们愿意以构建一些微服务的方式来进行添加,而这些微服务会去调用原先那个单体系统的 API。这种方法对于本质上是临时的功能尤其方便,例如报道体育赛事的专用页面。当使用快速开发语言时,像这样的网站就能被快速整合在一起,并在时间结束后删除。我们在金融机构看到了类似的做法,针对一个市场机会,添加新服务进来,并在几个月甚至几周后丢弃。 + +这种对可替换性的强调是模块化设计一般性原则的一个特例,即通过“演进式”模式推动模块化实现。大家都愿意将那些在同时发生变化[^14] 的东西,放到同一个模块中。很少变化的部分,应该放在不同的服务中,以区别那些当前正在经历大量变动的部分。如果您发现需要同时反复变更的两个服务时,那就表明他们应该合并。 + +将组件放入服务中可以为更细粒度的发布计划添加机会。对于单体应用,任何更改都需要完整构建和部署整个应用程序。但是对使用微服务,您只需要重新部署您修改的服务。这可以简化并加快发布过程。缺点是:必须考虑当一个服务发生变化时,依赖它并对其进行消费的其他服务将无法工作。传统的集成方法是尝试使用版本控制来解决这个问题,但微服务领域中,大家更喜欢使用版本控制作为[最后不得已的手段](https://martinfowler.com/articles/enterpriseREST.html#versioning)。我们可以通过将服务设计为对提供者变更,尽量能够容错来避免大量版本控制 + +## 未来的方向是“微服务”吗? + +我们写这篇文章的主要目的是解释微服务的主要思想和原则。通过花时间来做到这一点,我们清楚地认为微服务架构风格是一个重要的想法——值得认真考虑企业应用程序。我们最近使用这种方式构建了几个系统,并了解其他团队已经使用并支持这种方法。 + +我们了解到那些在某种程度上做为这种架构风格的实践先驱包括:亚马逊,Netflix,[Guardian](https://www.theguardian.com/) 和 [UK Government Digital Service](https://gds.blog.gov.uk/),[realeastate.com.au](https://martinfowler.com/articles/realestate.com.au),[comparethemarket.com](http://www.comparethemarket.com/)。2013 年的技术大会圈子充满了各种各样的,正在转向可以归类为微服务的公司——包括 Travis CI。此外,有很多组织长期以来一直在做我们称为微服务的东西,但没有使用过这个名字(通常被标记为 SOA——尽管如我们所说,SOA 有许多互相矛盾的形式[^15] ) + +然而,尽管有这些积极的经验,但我们并不认为我们确信微服务是软件架构的未来发展方向。虽然到目前为止我们的经验与单体应用相比是积极的,但我们意识到没有足够的时间让我们做出充分的判断。 + +![](https://martinfowler.com/articles/microservices/images/sam-book.jpg) +>我们的同事 Sam Newman 在 2014 年的大部分时间都在撰写一本书,该书描述了我们构建微服务的经验。如果想进一步了解该主题,这应该是您的下一步 + +通常,您的架构决策的真正后果只有在开发它几年后才会明显。我们已经由带着强烈模块化愿望的优秀团队所做的一些项目,最终却构建出一个单体架构,并在几年内不断腐化。许多人认为微服务不太可能出现这种衰退,因为服务边界是明确的,很难随意捣乱。然而,对于那些开发时间足够长的各种系统,除非我们已经见识的足够多,否则我们无法真正评估微服务架构是如何成熟的。 + +人们可能会期望微服务成熟得很好。在组件化的任何努力中,成功取决于在组件中的适用程度。很难弄清楚组件边界的确切位置。“演进式”设计承认难以对边界进行正确定位,因此它将工作的重点放到了易于对边界进行重构之上。但是当您的组件是具有远程通信的服务时。那么重构比适用进程内库要困难的多。跨服务边界移动代码很困难,任何接口更改都需要在参与者之间协调,需要添加向后兼容性,测试变得更加复杂。 + +另一个问题是,如果组件没有干净利落地组成一个系统,那么您所做的就是将复杂性从组件内部转移到组件之间的连接。这样做的后果,不仅仅是移动复杂性,而是将其移动到一个不那么明确且难以控制的地方。当你在一个小而简单的组件内部查看时,人们很容易认为事情已经变得更好了,然而却忽略了服务之间的杂乱连接 + +最后,还有团队技能的因素。新技术往往被技术更加过硬的团队所采用。对于技术更加过硬的团队更更有效的一项技术,并不一定适用于技术略逊一筹的团队。我们已经看到很多不太熟练的团队构建混乱的单体架构,当微服务发生这种混乱时,会出现什么情况?这需要花时间来观察。一个糟糕的团队,总是会创建一个糟糕的系统——很难说微服务是减少了杂乱,还是让事情变得更糟。 + +我们听到一个合理的说法,不应该一上来就以微服务架构作为起点。相反,从[单体应用开始](https://martinfowler.com/bliki/MonolithFirst.html),保持模块化。当单体系统出现问题时将其拆分为微服务。(虽然[这个建议并不理想](https://martinfowler.com/articles/dont-start-monolith.html),但是好的进程内接口通常不是一个好的服务接口) + +因此,我们谨慎乐观地写下这一点。到目前为止,我们已经看到了足够多的微服务风格,觉得它是[一条值得走的路](https://martinfowler.com/microservices/)。我们无法确定最终会在哪里结束,但软件开发的挑战之一是您只能根据您当前必须提供的不完善信息作出决策。 + +## 参考 + +虽然这不是一个详尽的列表,但是它们是微服务从业者可以从中吸取灵感来源,或者是那些倡导的理念与本所述内容详实的一些资料 + +博客和在线文章 +* [Clemens Vasters’ blog on cloud at microsoft](http://blogs.msdn.com/b/clemensv/) +* [David Morgantini’s introduction to the topic on his blog](http://davidmorgantini.blogspot.com/2013/08/micro-services-introduction.htm) +* [12 factor apps from Heroku](http://12factor.net/) +* [UK Government Digital Service design principles](https://www.gov.uk/design-principles) +* [Jimmy Nilsson’s blog](http://jimmynilsson.com/blog/) and [article on infoq about Cloud Chunk Computing](http://www.infoq.com/articles/CCC-Jimmy-Nilsson) +* [Alistair Cockburn on Hexagonal architectures](http://alistair.cockburn.us/Hexagonal+architecture) + +书籍 +* [Release it](https://www.amazon.com/gp/product/0978739213?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0978739213) +* [Rest in practice](https://www.amazon.com/gp/product/0596805829?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0596805829) +* [Web API Design (free ebook)](https://pages.apigee.com/web-api-design-ebook.html). Brian Mulloy, Apigee. +* [Enterprise Integration Patterns](https://www.amazon.com/gp/product/0321200683?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321200683) +* [Art of unix programming](https://www.amazon.com/gp/product/0131429019?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0131429019) +* [Growing Object Oriented Software, Guided by Tests](https://www.amazon.com/gp/product/0321503627?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321503627) +* [The Modern Firm: Organizational Design for Performance and Growth](https://www.amazon.com/gp/product/0198293755?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0198293755) +* [Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation](https://www.amazon.com/gp/product/0321601912?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321601912) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://www.amazon.com/gp/product/0321125215?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321125215) + +简报 +* [Architecture without Architects](https://www.youtube.com/watch?v=qVyt3qQ_7TA). Erik Doernenburg +* [Does my bus look big in this?](http://www.infoq.com/presentations/soa-without-esb). Jim Webber and Martin Fowler, QCon 2008 +* [Guerilla SOA](http://www.infoq.com/presentations/webber-guerilla-soa). Jim Webber, 2006 +* [Patterns of Effective Delivery](http://vimeo.com/43659070).Daniel Terhorst-North, 2011. +* [Adrian Cockcroft's slideshare channel.](http://www.slideshare.net/adrianco) +* [Hydras and Hypermedia](http://vimeo.com/28608667). Ian Robinson, JavaZone 2010 +* Justice will take a million intricate moves Leonard Richardson, Qcon 2008. +* [Java, the UNIX way](http://vimeo.com/74452550). James Lewis, JavaZone 2012 +* [Micro services architecture](http://yow.eventer.com/yow-2012-1012/micro-services-architecture-by-fred-george-1286). Fred George, YOW! 2012 +* [Democratising attention data at guardian.co.uk](http://gotocon.com/video#18). Graham Tackley, GOTO Aarhus 2013 +* [Functional Reactive Programming with RxJava](http://gotocon.com/video#6). Ben Christensen, GOTO Aarhus 2013 (registration required). +* [Breaking the Monolith](http://www.infoq.com/presentations/Breaking-the-Monolith). Stefan Tilkov, May 2012. + +论文 +* L. Lamport,[“The Implementation of Reliable Distributed Multiprocess Systems”](http://research.microsoft.com/en-us/um/people/lamport/pubs/implementation.pdf), 1978 +* L. Lamport, R. Shostak, M. Pease,["The Byzantine Generals Problem"](http://www.cs.cornell.edu/courses/cs614/2004sp/papers/lsp82.pdf), 1982 +* R.T. Fielding, ["Architectural Styles and the Design of Network-based Software Architectures"](http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm), 2000 +* E. A. Brewer, ["Towards Robust Distributed Systems"](http://www.cs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf), 2000 +* E. Brewer, ["CAP Twelve Years Later: How the 'Rules' Have Changed"](http://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed), 2012 + +## 总结 + +单体应用与微服务比较 + +### 单体应用 + +#### 特性 +* 调用方便,都是在一个进程内进行调用(针对于 Java 来说,就是运行在一个 JVM 上的应用) +* 部署方式简单, +* 事务处理方式可以容易处理 +* 由于都在一个进程内API 调用,不涉及网络的访问,因此出错的可能性要低很多 + +#### 优缺点 + +* 优点 + * 为人所熟知 + * 便于共享 + * 易于测试 + * 容易部署 +* 缺点 + * 复杂性逐渐变高 + * 技术债务逐渐上升 + * 部署速度逐渐变慢 + * 阻碍技术创新 + * **无法按需伸缩** + +### 微服务 + +#### 特性 + +* 每个微服务可独立运行在自己的进程里 +* 一系列独立运行的微服务共同构建起了这个系统 +* 每个服务为独立的业务开发,一个微服务一般玩某个特定的功能,比如:订单管理,用户管理等 +* 微服务之间通过一些轻量的通讯机制进行通信,比如通过 REST API 或者 RPC 的方式调用 + +#### 优缺点 + +* 优点 + * 易于开发和维护 + * 启动较快 + * 局部修改容易部署 + * 技术栈不受限 + * 按需伸缩 + * DevOps +* 缺点 + * 运维复杂 + * 数据一致性问题 + * 集成测试复杂 + * **重复代码** + * 监控困难 +* 挑战 + * **运维要求较高** + * 分布式的复杂性 + * 接口调整成本高 + * 重复你劳动 + +#### 设计原则 +* 单一职责原则 +* 服务自治原则 +* 轻量级通信原则 +* 接口明确原则 + +## 附录 +* [Microservices](https://martinfowler.com/articles/microservices.html) +* 校验 • [CeaserWang](https://github.com/1156721874) + +[^1]: The term "microservice" was discussed at a workshop of software architects near Venice in May, 2011 to describe what the participants saw as a common architectural style that many of them had been recently exploring. In May 2012, the same group decided on "microservices" as the most appropriate name. James presented some of these ideas as a case study in March 2012 at 33rd Degree in Krakow in [Microservices - Java, the Unix Way](http://2012.33degree.org/talk/show/67) as did Fred George [about the same time](https://www.slideshare.net/fredgeorge/micro-service-architecure). Adrian Cockcroft at Netflix, describing this approach as "fine grained SOA" was pioneering the style at web scale as were many of the others mentioned in this article - Joe Walnes, Daniel Terhorst-North, Evan Botcher and Graham Tackley. +[^2]: The term monolith has been in use by the Unix community for some time. It appears in [The Art of Unix Programming](https://www.amazon.com/gp/product/B003U2T5BA?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=B003U2T5BA) to describe systems that get too big. +[^3]: Many object-oriented designers, including ourselves, use the term service object in the [Domain-Driven Design](https://www.amazon.com/gp/product/0321125215?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321125215) sense for an object that carries out a significant process that isn't tied to an entity. This is a different concept to how we're using "service" in this article. Sadly the term service has both meanings and we have to live with the polyseme. +[^4]: We consider [an application to be a social construction](https://martinfowler.com/bliki/ApplicationBoundary.html) that binds together a code base, group of functionality, and body of funding. +[^5]: The original paper can be found on Melvyn Conway's website [here](http://www.melconway.com/Home/Committees_Paper.html). +[^6]: We can't resist mentioning Jim Webber's statement that ESB stands for ["Egregious Spaghetti Box"](http://www.infoq.com/presentations/soa-without-esb). +[^7]: Netflix makes the link explicit - until recently referring to their architectural style as fine-grained SOA. +[^8]: At extremes of scale, organisations often move to binary protocols - [protobufs](https://code.google.com/p/protobuf/) for example. Systems using these still exhibit the characteristic of smart endpoints, dumb pipes - and trade off transparency for scale. Most web properties and certainly the vast majority of enterprises don't need to make this tradeoff - transparency can be a big win. +[^9]: "YAGNI" or "You Aren't Going To Need It" is an [XP principle](http://c2.com/cgi/wiki?YouArentGonnaNeedIt) and exhortation to not add features until you know you need them. +[^10]: It's a little disengenuous of us to claim that monoliths are single language - in order to build systems on todays web, you probably need to know JavaScript and XHTML, CSS, your server side language of choice, SQL and an ORM dialect. Hardly single language, but you know what we mean. +[^11]: Adrian Cockcroft specifically mentions "developer self-service" and "Developers run what they wrote"(sic) in [this excellent presentation](http://www.slideshare.net/adrianco/flowcon-added-to-for-cmg-keynote-talk-on-how-speed-wins-and-how-netflix-is-doing-continuous-delivery) delivered at Flowcon in November, 2013. +[^12]: We are being a little disengenuous here. Obviously deploying more services, in more complex topologies is more difficult than deploying a single monolith. Fortunately, patterns reduce this complexity - investment in tooling is still a must though. +[^13]: In fact, Daniel Terhorst-North refers to this style as Replaceable Component Architecture rather than microservices. Since this seems to talk to a subset of the characteristics we prefer the latter. +[^14]: Kent Beck highlights this as one his design principles in [Implementation Patterns](https://www.amazon.com/gp/product/0321413091?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321413091). +[^15]: And SOA is hardly the root of this history. I remember people saying "we've been doing this for years" when the SOA term appeared at the beginning of the century. One argument was that this style sees its roots as the way COBOL programs communicated via data files in the earliest days of enterprise computing. In another direction, one could argue that microservices are the same thing as the Erlang programming model, but applied to an enterprise application context. \ No newline at end of file diff --git a/source/_posts/milepost1.md b/source/_posts/milepost1.md new file mode 100644 index 000000000..a47e852e4 --- /dev/null +++ b/source/_posts/milepost1.md @@ -0,0 +1,20 @@ +--- +title: 第 100 篇原创文章 +date: 2020-12-31 17:26:20 +categories: Milepost +tag: [note] +--- + +未曾想过,居然能写到第 100 篇文章。虽然大部分文章都是线性流水操作,但全部是自己经过**实践**的总结;虽然没有精彩的故事,但都是自己成长的**思考**;虽然有时一篇文章需要要长达一个多月的反复核对,但还是能默默**坚持**。只是这第 100 篇来的有点晚,断断续续大概有 3 年的时间,时间是个坏老头,把我给你写情话,悄悄的改成了谎话! + + + +回想起之前写文章主要是为了记录一些操作步骤和一些知识点,方便遇到类似问题,快速定位,解决问题。但随着文章的越写越多,包含的内容也越来越多,需要去了解的知识也越来越多,真的是有一种 “你知道的越多,你不知道的越多” 的感觉,这种感觉让我对待每个知识点都能有往深去深挖的动力,对每一个知识点用自己文字将它讲出来时有一种让我欲罢不能成就感,这或许就是上瘾吧 + +总结下第一个里程碑,主要是平时接触到领域算是一些入门级别的一些文章,以及一些比较粗浅的见闻,缺乏深层次的剖析和思考,这也是第二个里程碑首要做的事情,把每个接触到的知识点进行深挖,打通自己的技术栈。技术领域能不能走得远,很大程度上并不是你的技能宽度,而是深度,是在一个方向上的深耕,并且对于底层的技能也是要有足够的涉猎,只有这样就算是技术的花样任它怎么去变,你都能以不变应万变(透过现象找到本质);第二点是是对第一阶段内容完善补充;第三点就是打磨自己的语言表达能力,让文章更加的通俗易懂;第四点就是不能太拖拉,要保持高效的内容输出 + +这一阶段,对开源项目贡献评价最高的是 [rap2-delos](https://github.com/thx/rap2-delos/issues/119) 项目了。努力在接下来的里程中,提高质量和参与度,争取早日在大型项目中做到 Committer + +很感谢这一路走来,大伙对我的认可和期待以及赞赏,下一个里程碑我们见~ + +![](https://res.cloudinary.com/incoder/image/upload/v1611485411/blog/milepost1-admire.png) \ No newline at end of file diff --git a/source/_posts/minio.md b/source/_posts/minio.md new file mode 100644 index 000000000..c62f7ffd3 --- /dev/null +++ b/source/_posts/minio.md @@ -0,0 +1,379 @@ +--- +title: OSS 之 Minio 初体验 +date: 2021-03-16 15:30:10 +categories: Linux +tag: [FileUpdate] +--- + +MinIO 是一个基于 Apache License v2.0 开源协议使用 Go 语言开发的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。 + + + +MinIO 包含 MinIO Server, MinIO Client 以及方便开发基于不同编程语言使用的 MinIO SDK,这三部分组成,使用步骤也很简单,在服务器上安装 MinIO Server 应用,在项目中集成对应的 MinIO SDK,然后按照你的业务情况编写相应的实现即可,在开始前,我们先看看为什么我选择 MiniIO 作为自建的 OSS 服务 + +1. MinIO 由良好的存储机制 +2. 兼容 Amason 的 S3 分布式存储 +3. 天然的支持云原生 +4. 支持私有部署,可分布式,可单机,100%开源 +5. 友好简单的部署方式,提供管理页面 +6. 还可以配合其他的健康管理工具进行监控,比如 [Prometheus](https://docs.min.io/docs/how-to-monitor-minio-using-prometheus.html) + +## 安装 + +由于 MinIO Server 已经提供了 Docker 的安装镜像,那我们就以 Docker 安装为例,其他安装方式可参考官方教程 [MinIO Quickstart Guide](https://docs.min.io/docs/minio-quickstart-guide.html) + +关于 Docker 的安装这里不再赘述,Docker 相关详细的使用等知识,可参考我之前的文章 [Docker(一)]() + +```docker +# 1. 拉取 minio docker 镜像 +docker pull minio/minio +# 2. 运行 minio 服务 +docker run -p 9000:9000 --name minio \ + -v /opt/docker/minio/data:/data \ + -v /opt/docker/minio/config:/root/.minio \ + -d --restart=always \ + -d minio/minio server /data +``` + +>这里简单说一下命令的含义,应用命名为 minio ,运行服务在 9000 端口,同时将容器的相关路径文件映射到宿主机的 `/opt/docker/minio` 路径,开机自启 + +成功运行服务,可查看日志 + +```text +Endpoint: http://172.17.0.2:9000 http://127.0.0.1:9000 +Browser Access: +http://172.17.0.2:9000 http://127.0.0.1:9000 +Object API (Amazon S3 compatible): +Go: https://docs.min.io/docs/golang-client-quickstart-guide +Java: https://docs.min.io/docs/java-client-quickstart-guide +Python: https://docs.min.io/docs/python-client-quickstart-guide +JavaScript: https://docs.min.io/docs/javascript-client-quickstart-guide +.NET: https://docs.min.io/docs/dotnet-client-quickstart-guide +Detected default credentials 'minioadmin:minioadmin', please change the credentials immediately using 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' +``` + +安装完成后,我们就可以通过 http://localhost:9000 访问 MinIO 服务,默认用户名和密码分别为: minioadmin, minioadmin + +## 使用 + +### 页面操作 + +![](https://res.cloudinary.com/incoder/image/upload/v1617090692/blog/minio-web.png) + +我们直接看图,输入账号密码后,可以看到 MinIO 的管理页面,我们就可以上传文件,是不是很方便。第一次上传必须先要创建一个 bucket 后,才可以上传,如下图操作结果 + +![](https://res.cloudinary.com/incoder/image/upload/v1617091291/blog/minio-upload.png) + +### Client 操作 + +### SDK 操作 + +{% note warning %} +这里以 Java 语言为例,查看官方文档时,一定要查看英文文档,中文文档已年久失修落后很多,其他的语言实现请参考官方文档 +{% endnote %} + +#### 导入依赖 + +```groovy +dependencies { + implementation "io.minio:minio:8.1.0" +} +``` + +#### 功能实现 + +由于我这里是 SpringBoot 项目,为了方便在应用的 `application.yml` 文件中配置了 MinIO 相关的参数 + +#### 配置文件 + +```yaml +minio: + # minio 服务运行的地址 + endpoint: http://127.0.0.1 + # minio 服务运行的端口 + port: 9000 + # minio 服务登录账号 + accessKey: minioadmin + # minio 服务登录密码 + secretKey: minioadmin + # minio 设置上传默认存放桶 + bucketName: cpe-manager-test +``` + +#### 工具类 + +```java +/** + * 资源上传工具类 + * + * @author : Jerry xu + * @since : 2021/3/18 14:05 + */ +@Slf4j +@Component +public class MinioUtils { + + /** + * minio: + * endpoint: http://192.168.1.163 + * port: 9000 + * accessKey: minioadmin + * secretKey: minioadmin + * bucketName: cpe-manager-test + */ + @Value("${minio.endpoint}") + private static final String ENDPOINT = "http://192.168.1.163"; + @Value("${minio.port}") + private static final Integer PORT = 19000; + @Value("${minio.accessKey}") + private static final String ACCESS_KEY = "minioadmin"; + @Value("${minio.secretKey}") + private static final String SECRET_KEY = "minioadmin"; + @Value("${minio.bucketName}") + private static final String BUCKET_NAME = "cpe-manager-test"; + + private static MinioClient minioClient; + + public static MinioClient getInstance() { + if (minioClient == null) { + minioClient = MinioClient.builder().endpoint(ENDPOINT, PORT, false).credentials(ACCESS_KEY, SECRET_KEY).build(); + } + return minioClient; + } + + /** + * 获取minio所有的桶 + * + * @return java.util.List + * @throws Exception exception + */ + public static List getAllBucket() throws Exception { + // 获取minio中所以的 bucket + List buckets = getInstance().listBuckets(); + for (Bucket bucket : buckets) { + log.info("bucket 名称: {} bucket 创建时间: {}", bucket.name(), bucket.creationDate()); + } + return buckets; + } + + /** + * 将图片上传到minio服务器 + * + * @param inputStream 输入流 + * @param objectName 存储的文件名称,必须包含后缀 + * @param bucketName 自定义存储桶 + */ + public static String uploadToMinio(InputStream inputStream, String objectName, String bucketName) { + try { + // 获取文件后缀 + String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf(".")); + String contentType = FileType.getContentType(fileSuffix); +// // 重新生成文件名,避免重复 +// String objectName = UUID.randomUUID().toString() + fileSuffix; + long size = inputStream.available(); + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .stream(inputStream, size, -1) + .contentType(contentType) + .build(); + // 上传到minio + ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs); + inputStream.close(); + if (!StringUtils.isEmpty(objectWriteResponse.etag())) { + // 返回上传获取到的地址 + return getUrlByObjectName(objectName); + } + } catch (Exception e) { + log.error(e.getMessage()); + e.printStackTrace(); + } + return null; + } + + /** + * 将图片上传到minio服务器,默认存放在 cpe-manager-test 桶内 + * + * @param inputStream 输入流 + * @param objectName 存储的文件名称,必须包含后缀 + */ + public static String uploadToMinio(InputStream inputStream, String objectName) { + try { + // 获取文件后缀 + String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf(".")); + String contentType = FileType.getContentType(fileSuffix); +// // 重新生成文件名,避免重复 +// String objectName = UUID.randomUUID().toString() + fileSuffix; + long size = inputStream.available(); + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .bucket(BUCKET_NAME) + .object(objectName) + .stream(inputStream, size, -1) + .contentType(contentType) + .build(); + // 上传到minio + ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs); + inputStream.close(); + if (!StringUtils.isEmpty(objectWriteResponse.etag())) { + // 返回上传获取到的地址 + return getUrlByObjectName(objectName); + } + } catch (Exception e) { + log.error(e.getMessage()); + e.printStackTrace(); + } + return null; + } + + /** + * 根据指定的objectName获取下载链接,需要bucket设置可下载的策略 + * + * @param objectName 对象的名称 + * @return java.lang.String + */ + public static String getUrlByObjectName(String objectName) { + try { + return getInstance().getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(BUCKET_NAME) + .object(objectName) + // 过期策略【默认有效期7天】 +// .expiry(2, TimeUnit.HOURS) + .build()); + } catch (Exception e) { + log.error(e.getMessage()); + e.printStackTrace(); + } + return null; + } + + /** + * 根据objectName从minio中下载文件到指定的目录 + * + * @param objectName minio上的文件名称 + * @param fileName 下载生成的文件名 + * @param dir 文件目录 + * @throws Exception exception + */ + public static void downloadFromMinioToFile(String objectName, String fileName, String dir) throws Exception { + GetObjectArgs objectArgs = GetObjectArgs.builder() + .bucket(BUCKET_NAME) + .object(objectName) + .build(); + File file = new File(dir); + if (!file.exists()) { + if (file.mkdirs()) { + log.error("创建失败"); + } + } + InputStream inputStream = getInstance().getObject(objectArgs); + FileOutputStream outputStream = new FileOutputStream(new File(dir, fileName.substring(fileName.lastIndexOf("/") + 1))); + int length; + byte[] buffer = new byte[1024]; + while ((length = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, length); + } + outputStream.close(); + inputStream.close(); + } + + /** + * 根据文件名批量删除(默认删除 BUCKET_NAME 下的文件) + * + * @param listFile 文件名(含后缀)列表,例如:demo.png + * @return 成功返回为null, 失败返回Map + */ + @SneakyThrows + public static Map removeObjects(List listFile) { + List objects = new LinkedList<>(); + Map resultMap = new HashMap<>(); + listFile.forEach(t -> objects.add(new DeleteObject(t))); + Iterable> results = + getInstance().removeObjects( + RemoveObjectsArgs.builder() + .bucket(BUCKET_NAME) + .objects(objects) + .build()); + for (Result result : results) { + DeleteError error = result.get(); + resultMap.put(error.objectName(), error.message()); + log.error("Error in deleting:{}, message{}", error.objectName(), error.message()); + } + return resultMap; + } + +} +``` + +#### 上传接口 + +```java +@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) +@ApiOperation(value = "文件上传", notes = "支持多文件上传") +public List uploadTest(@ApiParam(value = "文件") @RequestParam("file") List file) { + // 上传的图片地址 + List successFile = new ArrayList<>(file.size()); + file.forEach(t -> { + try { + String url = MinioUtils.uploadToMinio(t.getInputStream(), t.getOriginalFilename()); + log.info("图片地址{}", url); + successFile.add(url); + } catch (IOException e) { + e.printStackTrace(); + } + }); + return successFile; +} + +``` + +#### 测试 + + + +## 问题 + +### bucket命名 + +创建 bucket 时,命名不可以使用下划线符号 "_" + +### 账号密码修改 + +通过网页管理页面修改登录账号及密码,提示 "Credentials of this user cannot be updated through MinIO Browser." ,原因是安装应用时,并未显示的指定用户名和密码,可在运行启动时添加如下配置 + +```bash +-e "MINIO_ROOT_USER=admin" \ +-e "MINIO_ROOT_PASSWORD=admin123456" \ +``` + +### 最长7天有效 + +通过网页管理页面共享图片或者是使用 SDK 上传图片得到的图片 URL 地址,有效期最长为7天 +``` + +mc config host add minio http://192.168.1.163:19000 minioadmin minioadmin --api S3v4 +mc policy set public minio/cpe-manager-test + +mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin --api S3v4 +mc policy set public minio/bucket (bucket修改成你自己的名字) +``` + +### 图片无法查看 + +1. 使用 SDK 上传时,需要注意设置content-type信息 +2. 无权限查看 + + +## 小结 + +关于 MinIO 还有很多知识点,本片只是站在使用者角度,把一些使用过程和问题进行了汇总,谈不上深度 + +## 参考 + +1. [Minio 手册](https://docs.min.io/cn/minio-quickstart-guide.html) +2. [Minio 示例](https://github.com/minio/minio-java/tree/release) +3. [Minio 修改密码](https://blog.csdn.net/tank99tank/article/details/109464325) +4. [Minio](https://www.jianshu.com/p/68ac0477291d) +5. [Minio 安装以及使用](https://www.cnblogs.com/gaohongyu/p/13986964.html) +6. [Minio 设置文件链接永久有效](https://blog.csdn.net/WuJiangang5112/article/details/112988074) \ No newline at end of file diff --git a/source/_posts/movie-fierce.md b/source/_posts/movie-fierce.md new file mode 100644 index 000000000..642b9df7a --- /dev/null +++ b/source/_posts/movie-fierce.md @@ -0,0 +1,24 @@ +--- +title: 《激战》 +date: 2018-10-03 00:02:00 +categories: Movie +tag: 激战 +--- + +{% cq %}怕,你就会输一辈子{% endcq %} + +喜欢其中的一些台词,大伙共勉 +* 其实,我每次上台都很怕的,不过每次我都会跟自己说,我能做到 +* 这场比赛我可能会跌倒,但我一定会站起来 +* 怕,你就会输一辈子 + + + +自己的一些感触: +其实很多时候,道理都懂,但却不能坚持下去,但这些道理都在自己生活中一点点的用生活感悟出来,那这些道理会更浓烈,更让人刻骨铭心 +* 尊重和珍惜,那些愿意为你去花时间的人 +* 要和自己志同道合,有共同目标的伙伴去互相较劲 +* 从哪里跌倒就要从哪里爬起来 +* 一路跌跌撞撞走下去,中间的酸甜苦辣是最美的味道 + + \ No newline at end of file diff --git a/source/_posts/mq-rabbit1.md b/source/_posts/mq-rabbit1.md new file mode 100644 index 000000000..36b362b5e --- /dev/null +++ b/source/_posts/mq-rabbit1.md @@ -0,0 +1,8 @@ +--- +title: MQ 系列 — RabbitMQ(一)环境搭建 +date: 2020-11-10 21:00:12 +categories: MQ +tag: [RabbitMQ] +--- + + \ No newline at end of file diff --git a/source/_posts/mq-rocket1.md b/source/_posts/mq-rocket1.md new file mode 100644 index 000000000..72058638b --- /dev/null +++ b/source/_posts/mq-rocket1.md @@ -0,0 +1,156 @@ +--- +title: MQ 系列 — RocketMQ(一)环境搭建 +date: 2020-11-10 21:30:30 +categories: MQ +tag: [RocketMQ] +--- + +本篇我们来看 MQ 系列的另一个广泛使用的中间件 [RocketMQ](https://rocketmq.apache.org)。官方介绍到 “Apache RocketMQ™ 是一个统一的消息传递引擎,轻量级的数据处理平台。Apache RocketMQ 是一个分布式消息传递和流媒体平台,具有低延迟,高性能和可靠性,万亿级容量和灵活的可伸缩性” 。更重要的是在分布式消息队列中,目前唯一提供完整的事务消息的,只有 RocketMQ。 + + + +{% note info %} +* ~~RocketMQ 3.0.8 以及之前的版本是 支持分布式事务消息(找不到对应的提交记录)~~ +* ~~RocketMQ 3.0.8 之后,分布式事务的阉割了,不支持分布式事务消息(找不到对应的提交记录)~~ +* [RocketMQ 4.0.0 开始 Apache 孵化,但是也不支持分布式事务消息](https://rocketmq.apache.org/release_notes/release-notes-4.0.0-incubating/) +* [RocketMQ 4.3.0 又开始支持分布式事务消息](https://rocketmq.apache.org/release_notes/release-notes-4.3.0/) +{% endnote %} + +## 基本概念 + +RocketMQ 由四部分组成:name servers, brokers, producers and consumers。它们中的每一个都可以在没有单个故障点的情况下进行水平扩展 + +### name servers + +用来保存 Broker 相关 Topic 等元信息并给 Producer,提供 Consumer 查找 Broker 信息。主要包括两个功能: + +1. Broker 管理,NameServer 接受来自经纪人群集的注册,并提供心跳机制以检查经纪人是否还活着 +2. Routing 管理,每个NameServer 将保存有关代理群集的完整路由信息以及客户端查询的队列信息 + +### brokers + +负责消息的存储和传递,消息查询,HA 保证等(消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息)。Broker 服务器具有几个重要的子模块: + +* Remoting Module:处理来自客户端的请求 +* Client Manager:管理客户(生产者/消费者)并维护消费者的主题订阅 +* Store Service:提供简单的 API,以在物理磁盘中存储或查询消息 +* HA Service:提供主代理(master broker)和从代理(slave broker)之间的数据同步功能 +* Index Service:通过指定的键为消息建立索引并提供快速的消息查询 + +### producers + +负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。支持分布式部署,分布式生产者通过多种负载平衡模式将消息发送到 Broker 集群。发送过程支持快速失败并且延迟低 + +### consumers + +负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序。支持 “推和拉” 模型中的分布式部署。它还支持集群使用和消息广播。它提供了实时消息订阅机制,可以满足大多数消费者的需求 + +## 整体流程 + +## 准备工作 + +* Linux +* JDK8+ +* Maven3.2.x+ +* Git + +>相关工具没安装可参考 [Linux 常用应用安装](https://incoder.org/2018/05/15/linux-build/) + +## 单机部署 + +单机部署,主要是进行 RocketMQ 的简单使用,因此没有必要分配较大内存空间,RocketMQ NameServer 默认会占用 **4G**,因此在启动部署时会调整 JVM 的相关参数,指定分配内存空间 + +### 普通部署 + +#### RocketMQ 部署 + +1. Nameserver + ```bash + # 程序存放位置,根据喜好 + cd /home/application + # 下载应用 + wget https://archive.apache.org/dist/rocketmq/4.7.1/rocketmq-all-4.7.1-bin-release.zip + # 解压文件,并进入解压后的目录,进行查看目录概要等信息(没有 unzip 命令,请 yum install unzip) + unzip rocketmq-all-4.7.1-bin-release.zip && cd rocketmq-all-4.7.1-bin-release/ && ls -l + # 进入启动目录 + cd bin/ + + # 编辑启动脚本文件,修个相应的 JVM 参数 + vim runserver.sh + ### 定位到: JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=1 28m -XX:MaxMetaspaceSize=320m" + ### 更改为: JAVA_OPT="${JAVA_OPT} -server -Xms512M -Xmx512M -Xmn256M -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" + + # 修个完成后启动 nameserver 应用 + nohup ./mqnamesrv & + ``` +2. 启动 broker + ```bash + # 进入 bin 目录 + cd /home/application/rocketmq-all-4.7.1-bin-release/bin/ + + # 编辑启动脚本文件,修个相应的 JVM 参数 + vim runbroker.sh + ### 定位到: JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g" + ### 更改为: JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn512m" + + # 修个完成后,后台启动 broker,-n 指定 NameServer 服务ip地址 + nohup ./mqbroker -n localhost:9876 & + ``` +3. 验证 RocketMQ + ```bash + # 使用 clusterList 命令来查看集群的状态 + sh /home/application/rocketmq-all-4.7.1-bin-release/bin/mqadmin clusterList -n 127.0.0.1:9876 + ``` + +#### RocketMQ-Console 部署 + +通过命令去操作 RocketMQ,其实是比较麻烦,没有图形化来的直观和方法。为此 RocketMQ 官方提供了一个运维管理界面 RokcetMQ-Console-Ng,用于对 RocketMQ 集群提供常用的运维功能 + +>基于 SpringBoot 开发 + +```bash +wget https://github.com/apache/rocketmq-externals/archive/rocketmq-console-1.0.0.tar.gz +tar -xf rocketmq-console-1.0.0.tar.gz +# 重命名,为了方便后续操作 +mv rocketmq-externals-rocketmq-console-1.0.0/rocketmq-console rocketmq-consoe +cd rocketmq-console + +# 编辑配置文件 +vim src/main/resources/applications.properties +### 修改指向的 nameserver 地址 +### rocketmq.config.namesrvAddr=127.0.0.1:9876 + +# 使用 maven 命令编译源代码 +mvn clean package -DskipTests +# 复制包到自己常用的软件安装目录 +cp rocketmq-console-ng-1.0.0.jar /opt/application/ +# 启动 rocketmq-conolse +nohup java -jar rocketmq-console-ng-1.0.0.jar & +``` + +正常启动后,访问:http://localhost:8080 查看是否安装成功 + +{% note info %} +如果你使用的 root 用户启动 rocketmq, rocketmq-console 应用,那么他们的日志分别在 +* rocketmq: /home/root/logs/rocketmqlogs/ +* rocketmq-console: /home/root/logs/consolelogs +{% endnote %} + +### Docker 部署 + +截止 2020-11-10,官方的镜像依然还是 4.6 版本,难道又是阿里没人维护的 KPI 🙄 + +[RocketMQ-Docker](https://github.com/apache/rocketmq-docker) + +## 分布式部署 + +### 普通部署 + +### Docker 部署 + +## 参考 + +1. [《Apache RocketMQ 从入门到实战》.pdf](https://developer.aliyun.com/ebook/ranking) +2. [芋道 RocketMQ 极简入门](https://www.iocoder.cn/RocketMQ/install/) +3. [芋道 Spring Boot 消息队列 RocketMQ 入门](https://www.iocoder.cn/Spring-Boot/RocketMQ/) +4. [RocketMQ 4.7.1 环境搭建、集群、SpringBoot整合MQ](https://www.cnblogs.com/chenyanbin/p/13798952.html) \ No newline at end of file diff --git a/source/_posts/mysql1.md b/source/_posts/mysql1.md new file mode 100644 index 000000000..b1bbaf1b3 --- /dev/null +++ b/source/_posts/mysql1.md @@ -0,0 +1,195 @@ +--- +title: MySQL 必备技能 +date: 2019-11-01 00:01:00 +categories: [DataBase, MySQL] +tag: [MySQL] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1573002782/blog/mysql-skill.png) + + + +## SQL 语句 + +SQL全称(Structured Query Language):是一种特定目的编程语言,用于管理关系数据库管理系统(RDBMS),或在关系流数据管理系统(RDSMS)中进行流处理 +也就是一种数据库查询和程序设计语言,用于存取数据以及查询和管理关系型数据库 + +### SQL 规则 + +1. SQL 语句可以单行或多行书写,以分号 `;` 结尾 +2. 可以使用空格和缩进来增强语句可读性 +3. MySQL数据库的 SQL 语句不区分大小写,**关键字建议大写** + +### SQL 分类 + +![SQL_Commands](https://res.cloudinary.com/incoder/image/upload/v1582687197/blog/SQL_Commands.png) + +#### DDL + +Data Definition Language(DDL):数据定义语言,用来创建数据库中的表,索引,视图,存储过程,触发器等。 + +##### 操作数据库 + +* CREATE:创建 + ```sql + # 创建数据库 + CREATE DATABASE 数据库名称; + # 创建数据库,判断是否存在,不存在则创建 + CREATE DATABASE IF NOT EXISTS 数据库名称; + # 创建数据库并指定其字符集 + CREATE DATABASE 数据库名称 CHARACTER SET 字符集; + ``` +* ALERT:修改 + ```sql + # 修改数据库的字符集 + ALTER DATABASE 数据库名称 CHARACTER SET 字符集名称; + ``` +* DROP:删除 + ```sql + # 删除数据库 + DROP DATABASE 数据库名称; + # 判断数据库存在,存在则删除 + DROP DATABASE IF EXISTS 数据库名称; + ``` +* 查询 + ```sql + # 查询所有数据库的名称 + SHOW DATABASES; + # 查询某个数据库的创建语句 + SHOW CREATE DATABASE 数据库名称; + ``` + +##### 操作表 + +* CREATE:创建 + ```sql + # 语法 + CREATE TABLE tableName( + 列名1 数据类型1, + 列名2 数据类型2, + .... + 列名n 数据类型n, + [添加约束...] + ); + # 示例 + ``` +* ALERT:修改 + ```sql + # 修改表名 + ALTER TABLE 表名 RENAME TO 新表名; + # 修改表的字符集 + ALTER TABLE 表名 CHARACTER SET 字符集名称; + # 添加一列 + ALTER TABLE 表名 ADD 列名 数据类型; + # 修改列名 类型 + ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型; + # 只修改数据类型 + ALTER TABLE 表名 MODIFY 列名 新数据类型; + # 删除列 + ALTER TABLE 表名 DROP 列名; + ``` +* DROP:删除 + ```sql + # 删除表 + DROP TABLE 表名; + # 判断表是否存在,存在则删除 + DROP TABLE IF EXISTS 表名; + ``` + +TRUNCATE 和 DELETE 区别 +{% note info %} +1. TRUNCATE TABLE 表名 语句在功能上与不带 WHERE 子句的 DELETE 语句相同;二者均删除表中的全部数据,但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少 +2. DELETE 语句每次删除一行,并在事务日志中为所删除的每一行记录。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且旨在事务日志中记录页的释放 +3. TRUNCATE TABLE 删除表中的所有行,但表结构及其列,约束,索引等保存不变,且会**重置表的计数**(通常我们作为表的主键);DELETE TABLE **不会重置计数**;如果要删除表定义及其数据,使用 DROP TABLEE 语句 +{% endnote %} + +#### DML + +Data Manipulation Language(DML):数据操作语言,用来改变数据库数据 + +* INSERT + ```sql + # 插入 + INSERT INTO 表名(字段列表) VALUES(值列表) + # 拷贝表 + INSERT INTO 新表名 SELECT * FROM 表名; + CREATE TABLE 新表名 LIKE 表名; + ``` + +* UPDATE + ```sql + UPDATE 表名 SET 字段1=值1,字段n=值n [WHERE 条件] [ORDER BY 字段名 ASC|DESC] [LIMIT]; + ``` +* DELETE + ```sql + DELETE FROM 表名 [WHERE 条件] [ORDER BY 字段名 ASC|DESC] [LIMIT]; + ``` + +#### DQL + +Data Query Language(DDL):数据查询语言,用于建立,修改,删除数据库中的各种对象 + +```sql +SELECT +column_1,column_2,... +FROM table_1 +[INNER | LEFT |RIGHT] JOIN table_2 ON CONDITIONS +WHERE conditions +GROUP BY column_1 +HAVING group_conditions +ORDER BY column limit offset,length +``` + +#### DCL + +Data Control Language(DCL):数据控制语言 + +* GRANT +* REVOKE + +#### TCL + +Transaction Control Language(TCL): 事务控制语言,用于维护数据的一致性 + +### 索引 + +### 字符集 + +## 常用命令 + + +## 锁表处理 + +### 方法一 + +1. 查看是否锁表 + ```sql + show OPEN TABLES where In_use > 0; + ``` +2. 查看进程,查找被锁表的进程ID + ```sql + show processlist; + ``` +3. kill 锁表的进程 ID + ```sql + kill id; + ``` + +### 方法二 + +1. 查看当前数据库的锁表情况 + ```sql + SELECT * FROM information_schema.INNODB_TRX; + ``` +2. 杀掉查询结果中锁表的trx_mysql_thread_id + ```sql + kill trx_mysql_thread_id + ``` + +## MySQL 用户分配 + +![mysql-account](https://res.cloudinary.com/incoder/image/upload/v1574820841/blog/mysql-account.png) + +## 参考 + +* [再见乱码:5分钟读懂MySQL字符集设置](https://www.cnblogs.com/chyingp/p/mysql-character-set-collation.html) \ No newline at end of file diff --git a/source/_posts/netty-grpc.md b/source/_posts/netty-grpc.md new file mode 100644 index 000000000..2e860f5cd --- /dev/null +++ b/source/_posts/netty-grpc.md @@ -0,0 +1,32 @@ +--- +title: Netty(四)之 gRPC +date: 2020-04-12 08:03:10 +categories: Netty +tag: [Netty, gRPC] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1586693547/blog/grpc.png) + + + +[gRPC](https://grpc.io/) is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services. + +gPRC 是高性能,开源通用RPC框架,可以运行在任何环境中,它是可插拔的并且支持负载均衡,跟踪,运行状况检查和身份校验,从而有效地连接数据中心和跨数据中心的服务,它也适用于分布式计算的最后一段,以将设备,移动应用和浏览器连接到后端的服务 + +gRPC 可以使用 Protocol buffers 作为接口定义语言(IDL:Interface Definition Language)和基础的消息交换格式,通常来说,你可以使用 proto2 这个版本,但是我们建议你使用 proto3 这个版本和 gRPC 一起使用,支持完整的语言,同时避免客户端和服务端版本不一致出现的其他问题 + +## gRPC + +[proto3 语言官方使用手册](https://developers.google.com/protocol-buffers/docs/proto3) + +### proto3 指南 + +### + +## gRPC实践 + +### gRPC下载 + +### 编译器安装 + +### 编写.proto 文件 diff --git a/source/_posts/netty-protobuf.md b/source/_posts/netty-protobuf.md new file mode 100644 index 000000000..ac4b4fc95 --- /dev/null +++ b/source/_posts/netty-protobuf.md @@ -0,0 +1,272 @@ +--- +title: Netty(二)之 Protobuf +date: 2019-11-29 20:32:10 +categories: Netty +tag: [Netty, Protobuf] +--- + +Netty 框架中已经默认支持了 Protobuf 格式的数据传输,因此我们本节就来学习 Protobuf,Protobuf 主要用于进行 RPC 数据传输(它是一种自定义协议,这种协议能更好,更小体积,对数据编解码【序列号和反序列化的过程】),在学习 Protobuf 之前我们先了解两个概念 RMI 和 RPC + + + +RMI:Remote Method Invocation,用于跨机器方法调用,只针对于 Java(要求调用者和被调用者都必须是 Java 程序) +* client:stub(装) +* server:skeleton(骨架) +client 与 server 底层通过 socket 数据传输 + +RPC:Remote Procedure Call,远程过程调用,原理和 RMI 一致,优势在于跨语言支持 + +那对于 RMI 和 RPC 编写的具体步骤如下: +1. 定义接口说明文件(IDL:Interface Description Language ):描述对象(结构体),对象成员,接口方法等一系列信息 +2. 通过 RPC 框架所提供的编译器,将说明文件编译成具体语言文件 +3. 在客户端与服务器端分别引入 RPC 编译器所生产的文件,即可享调用本地方法一样调用远程方法 + +## 序列化与反序列化 + +序列化与反序列化也叫做,编码与解码 + +序列化:将对象转换成字节,这个过程是encode +反序列化:将字节翻译成对象,这个过程是decode + +## Protobuf + +* 官方网站:[Protocol Buffers](https://developers.google.com/protocol-buffers) +* 官方指南:[Guide](https://developers.google.com/protocol-buffers/docs/overview) +* 官方说明:Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.(Protocol buffers 是一种语言中立,平台中立,可扩展的一种机制用于序列化结构化的数据) + +### Protobuf 编译环境搭建 + +1. 下载对应系统的编译器,格式如 `protoc-$VERSION-$PLATFORM.zip`,这里下载的是 `protoc-3.11.0-osx-x86_64.zip` +2. 为了使用方便,我们需要将 protoc 解压的路径添加到环境变量中 +3. 在终端中使用 `protoc -h` 命令验证 protoc 变量是否配置正确 + +### Protobuf 特定语言 + +这是一步可选步骤,根据自身需要,选择需要的语言编译文件,这里下载的是 `protobuf-java-3.11.0.zip`,用于学习了解 protoc 对 Java 编译的支持原理等 + +### Protobuf 使用 + +在官方 README 介绍中,请查看[Protobuf Runtime Installation](https://github.com/protocolbuffers/protobuf/blob/master/README.md)说明,这里介绍了在使用不同语言时需要安装的一些依赖,比如这里查看 [Java](https://github.com/protocolbuffers/protobuf/tree/master/java),在需要使用的项目中引入相关的依赖 + +#### 简单使用 + +1. 编写`.proto`文件 + ```proto + syntax = 'proto2'; + + package org.incoder.protoc; + + option optimize_for = SPEED; + option java_package = "org.incoder.protobuf"; + option java_outer_classname = "HelloProtobuf"; + + + message World { + required string name = 1; + optional string address = 3; + } + ``` +2. 执行编译命令,`protoc --java_out=$DST_DIR $SRC_DIR/FILE_NAME.proto` +3. 编写简单的测试,明白 RPC 的过程 + ```java + public static void main(String[] args) throws InvalidProtocolBufferException { + /////////////////////////////////////////////////////////////////////////// + // 把下面的这个过程等同到 RPC 的过程 + /////////////////////////////////////////////////////////////////////////// + + // A机器上构建了World对象 + HelloProtobuf.World world = HelloProtobuf.World.newBuilder() + .setName("China") + .setAddress("处于地球东半球") + .build(); + + // A 机器构建的对象转换成字节数组 + // 字节数组通过网络传输(Netty 等方式) A 机器传输到 B 机器 + byte[] world2ByteArray = world.toByteArray(); + + // B 机器上把字节数转换成对象(取决于在 B 机器上的使用语言),并把数据打印出来 + HelloProtobuf.World worlds = HelloProtobuf.World.parseFrom(world2ByteArray); + System.out.println(worlds); + } + ``` + +整个过程如下截图 +![](https://res.cloudinary.com/incoder/image/upload/v1575096751/blog/netty-protobuf-hello.png) + +#### 在 Netty 中的应用(单消息) + +和之前[Netty初体验(一)]()中编写步骤一样,这里只是对Initializer 中使用Netty 提供相关 Protobuf 的工具类 + +* ProtobufDecoder:将收到的 ByteBuf 解码为 Google Protocol Buffers 和 MessageLite(),请注意,如果使用基于流的传输方式(比如:TCP/IP),则此解码器必须与适当的 ByteToMessageDecoder(如:ProtobufVarint32FrameDecoder 或者 ProtobufVarint32LengthFieldPrepender) +* ProtobufDecoderNano:将接收到的 ByteBuf解码为 Google Protocol Buffers MessageNano,请注意,如果使用的是基于流的传输方式(如:TCP/IP),则此解码器必须与适当的 ByteToMessageDecoder(如果:LengthFieldBasedFrameDecoder)一起使用 +* ProtobufEncoder:将请求的 Google Protocol Buffers 和 MessageLite 编码为 ByteBuf +* ProtobufEncoderNano:将请求的 Google Protocol Buffers MessageNano 编码为 ByteBuf +* [ProtobufVarint32FrameDecoder](https://developers.google.com/protocol-buffers/docs/encoding#varints):解码器按消息中 Google Protocol Buffers 基于 128 Varints 整数长度字段的值动态拆分接收到的 ByteBuf + ``` + For example: + BEFORE DECODE (302 bytes) AFTER DECODE (300 bytes) + +--------+---------------+ +---------------+ + | Length | Protobuf Data |----->| Protobuf Data | + | 0xAC02 | (300 bytes) | | (300 bytes) | + +--------+---------------+ +---------------+ + ``` +* [ProtobufVarint32LengthFieldPrepender](https://developers.google.com/protocol-buffers/docs/encoding?csw=1#varints):一种编码器,可在 Google Protocol Buffers Base 128 Varints 之前添加 + ``` + BEFORE ENCODE (300 bytes) AFTER ENCODE (302 bytes) + +---------------+ +--------+---------------+ + | Protobuf Data |-------------->| Length | Protobuf Data | + | (300 bytes) | | 0xAC02 | (300 bytes) | + +---------------+ +--------+---------------+ + ``` + +##### SingleClient + +```java +public class SingleClient { + + public static void main(String[] args) throws InterruptedException { + EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); + + try { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class) + .handler(new SingleClientInitializer()); + + ChannelFuture channelFuture = bootstrap.connect("localhost", 5555).sync(); + channelFuture.channel().closeFuture().sync(); + } finally { + eventLoopGroup.shutdownGracefully(); + } + } +} +``` + +##### SingleClientInitializer + +```java +public class SingleClientInitializer extends ChannelInitializer { + + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + + pipeline.addLast(new ProtobufVarint32FrameDecoder()); + pipeline.addLast(new ProtobufDecoder(NettyDataInfo.Person.getDefaultInstance())); + pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); + pipeline.addLast(new ProtobufEncoder()); + + pipeline.addLast(new SingleClientHandler()); + } +} +``` + +##### SingleClientHandler + +```java +public class SingleClientHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, NettyDataInfo.Person msg) throws Exception { + + } + + /** + * 客户端建立连接后发送消息给服务端 + * + * @param ctx ctx + * @throws Exception exception + */ + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + NettyDataInfo.Person person = NettyDataInfo.Person.newBuilder() + .setName("netty") + .setAge(20) + .setAddress("https://netty.io") + .build(); + + // 发送消息给服务器 + ctx.channel().writeAndFlush(person); + } +} +``` + +##### SingleServer + +```java +public class SingleServer { + + public static void main(String[] args) throws InterruptedException { + EventLoopGroup bossGroup = new NioEventLoopGroup(); + EventLoopGroup workGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(new SingleServerInitializer()); + + ChannelFuture channelFuture = bootstrap.bind(5555).sync(); + channelFuture.channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + } + } +} +``` + +##### SingleServerInitializer + +```java +public class SingleServerInitializer extends ChannelInitializer { + + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + + pipeline.addLast(new ProtobufVarint32FrameDecoder()); + pipeline.addLast(new ProtobufDecoder(NettyDataInfo.Person.getDefaultInstance())); + pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); + pipeline.addLast(new ProtobufEncoder()); + + pipeline.addLast(new SingleServerHandler()); + } +} + +``` + +##### SingleServerHandler + +```java +public class SingleServerHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, NettyDataInfo.Person msg) throws Exception { + // 打印客户端连接后发送的消息 + System.out.println(msg.getName()); + System.out.println(msg.getAge()); + System.out.println(msg.getAddress()); + } +} +``` + +#### 在 Netty 中的应用(多消息) + +由于通过 Netty(底层是 socket)使客户端与服务端建立连接,使用 Google Protocol Buffers 协议进行数据通信,而 `ProtobufDecoder(MessageLite prototype)` 需要指定具体的实例,因此想要进行多消息类型数据通信,可以有两种方式 +1. 自定义通信协议 +2. 在定义 IDL 时,将所有类型的数据进行定义,最终生成一个包含了通信所需的所有类型的顶层 Message + +方式二具体代码可参考:[multiple](https://github.com/RootCluster/rc-cluster-netty/blob/master/src/main/java/org/incoder/netty/protobuf/multiple) + +## 问题 + +### 环境搭建问题 + +在配置好环境变量后,执行 `protoc -h` 命令提示 `“protoc” cannot be opened because the developer cannot be verified.` +![](https://res.cloudinary.com/incoder/image/upload/v1575080551/blog/netty-protobuf-install.png) + +* 原因:在 macOS 10.15 版本上未授权访问 +* 解决:在系统设置中,进行授权,操作如下 Settings -> Security & Privacy -> General + ![](https://res.cloudinary.com/incoder/image/upload/v1575080551/blog/netty-protobuf-mac-allow.png) +* 验证:在终端中执行 `protoc -h` 命令 + ![](https://res.cloudinary.com/incoder/image/upload/v1575080551/blog/netty-protobuf-mac-open.png) diff --git a/source/_posts/netty-thrift.md b/source/_posts/netty-thrift.md new file mode 100644 index 000000000..fad0690df --- /dev/null +++ b/source/_posts/netty-thrift.md @@ -0,0 +1,316 @@ +--- +title: Netty(三)之 Thrift +date: 2019-12-01 08:03:10 +categories: Netty +tag: [Netty, Thrift] +--- + +The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages. + +Apache Thrift软件框架,用于可扩展的跨语言服务开发,它包含软件栈和一个代码生成器用于构建服务,这个服务可以高效并且无缝的在 C++,Java,Python,PHP,Ruby,Erlang,Perl,Haskell,C#,Cocoa,Node.js,Smalltalk,OCaml 和 Delphi 等其他语言间协作 + + + +[Apache Thrift](http://thrift.apache.org) 与 [Google Protocal Buffers](https://developers.google.com/protocol-buffers) 都是一种可以用于在 Netty 之上的一种数据格式,Thrift 可应用的语言比 Protocal Buffers 多,并且 Thrift 除了用于传递数据的定义,底层还提供了传输层,因此可以单独的去使用,而不必强制运行在 Netty 载体之上 + +## Thrift + +### Thrift数据类型 + +Thrift不支持无符号类型,因为很多编程语言不存在无符号类型,比如Java + +* byte:有符号字节 +* i16:16位有符号整数 +* i32:32位有符号整数 +* i64:64位有符号整数 +* double:64位浮点数 +* string:字符串 +* bool:布尔值 + +### Thrift容器类型 + +* list:一系列由T类型的数据组成有序列表,元素可以重复 + >集合中的元素可以是除了service之外的任何类型,包括exception +* set:一系列由T类型的数据组成的无序列表,元素不可以重复 +* map:一个字典结构,key 为 K类型,value为 V类型,相当于Java中的 HashMap + +### Thrift支持的三类组件 + +#### struct + +结构体,编译生成完成后,对应的是我们的类,就像 C 语言一样,thrift 支持 struct 类型,目的就是将一些数据聚合在一起,方便传输管理。struct 的定义形式如下: + +```java +struct People{ + 1:string name; + 2:i32 age; + 3:string gender; +} +``` + +枚举,枚举的定义形式和 Java 的 Enum 定义类似 +```java +enum Gender{ + MALE, + FEMALE +} +``` + +#### exception + +异常,客户端与服务端之间通信用到的接口可能抛出的异常,thrift 支持自定义 exception,规则与 struct 一样 +```java +exception RequestException{ + 1:i32 code; + 2:string reason; +} +``` + +#### service + +服务,客户端与服务端之间通信用到的接口,thrift 定义服务相当于 Java 中创建 interface 一样,创建的 service 经过代码生成命令之后就会生成客户端和服务端的框架代码 +```java +service HelloWorldService{ + // service 中定义的函数,相当于 Java interface 中定义的方法 + string doAction(1:string name, 2:i32 age); +} +``` + +### 类型定义 + +thrift 支持类似 C++ 一样的 typedef 定义,在定义完别名后,在后面的 IDL 文件中就可以使用别名进行编写 +```c++ +// 把 i32 别名成 int +typedef i32 int +// 把 i64 别名成 long +typedef i64 long +``` + +### 常量 + +thrift 也支持常量定义,使用 const 关键字 +```java +const i32 MAX_RETRIES_TIME = 10 +const string MY_WEBSITE = "https://incoder.org" +``` + +### 命名空间 + +thrift 的命名空间相当于 Java 中的 package 的意思,主要目的是组织代码。thrift 使用关键字 namespace 定义命名空间 + +```java +// 格式:namespace 语言名 路径 +namespace java org.incoder.thrift +``` + +### 文件包含 + +thrift 也支持文件包含,相当于 C/C++ 中的 include,Java 中的 import,使用关键字 include 定义 +```C +include "global.thrift" +``` + +### 可选与必选 + +thrift 提供两个关键字`required`,`optional`,分别用于表示对应的字段是必填还是可选,主要根据你的业务来选择,推荐使用 optional +```java +1:required string name; +2:optional i32 age; +``` + +### Thrift工作原理 + +数据之间的传输使用socket(多种语言均支持),数据载以特定的格式(String等)发送,接收方进行语言解析。通过定义 Thrift 文件,由 Thrift 文件(IDL)生成双方语言的接口,model,在生成的model及接口中会有解析码,编码的代码 + +## Thrift 实践 + +### 下载Thrift + +这一步可以直接通过 maven 或者 gradle 的方式集成 Thrift 包到所需要的项目包管理中即可 + +```xml +# maven + + org.apache.thrift + libthrift + 0.13.0 + + +# gradle +compile 'org.apache.thrift:libthrift:0.13.0' +``` + +### 编译器安装 + +对于 macOS 可使用官方提供的方式去安装,也可以借助于 macOS 上,优秀的包管理工具 [Homebrew](https://brew.sh) 来进行安装,我这里就直接使用 Homebrew 进行安装,其他系统可参考[官方文档](http://thrift.apache.org/docs/install) + +```bash +brew install thrift +``` + +### 编写.thrift文件 + +编写.thrift 文件的指南,可以参考[官网文档](http://thrift.apache.org/docs/idl),编写完文件,使用 thrift 编译器提供的命令生成相关的代码 + +```bash +thrift --gen +``` + +## 示例 + +{% tabs Tags %} + + +{% code %} +// 定义命名空间 +namespace java org.incoder.thrift.java +namespace py org.incoder.thrift.py + +// 定义别名 +typedef i16 short +typedef i32 int +typedef i64 long +typedef bool boolean +typedef string String + +// 定义 struct +struct Person{ + 1: optional String username, + 2: optional int age, + 3: optional boolean married +} + +// 定义 exception +exception DataException{ + 1: optional String message, + 2: optional String callStack, + 3: optional String date +} + +// 定义 service +service PersonService{ + Person getPersonByUsername(1: required String username) throws (1: DataException dataException), + + void savePerson(1: required Person person) throws (1: DataException dataException) +} +{% endcode %} + + + +{% code lang:py %} +try: + person_handler = PersonHandler() + processor = PersonService.Processor(person_handler) + + serverSocket = TSocket.TServerSocket(port=9090) + transportFactory = TTransport.TFramedTransportFactory() + protocolFactory = TCompactProtocol.TCompactProtocolFactory() + + server = TServer.TSimpleServer(processor, serverSocket, transportFactory, protocolFactory) + server.serve() + +except Thrift.TException as tx: + print(tx.message) +{% endcode %} + + + +{% code lang:py %} +try: + tSocket = TSocket.TSocket('localhost', 9090) + tSocket.setTimeout(600) + + transport = TTransport.TFramedTransport(tSocket) + protocol = TCompactProtocol.TCompactProtocol(transport) + client = PersonService.Client(protocol) + + transport.open() + person = client.getPersonByUsername("张三") + print("username:" + person.username) + print("age:" + str(person.age)) + print("married:" + str(person.married)) + + print("------------------------") + newPerson = ttypes.Person() + newPerson.username = "李四" + newPerson.age = 30 + newPerson.married = True + + client.savePerson(newPerson) + transport.close() +except Thrift.TException as tx: + print(tx.message) +{% endcode %} + + + +{% code lang:java %} +public static void main(String[] args) throws Exception { + TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(9090); + THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4); + PersonService.Processor processor = new PersonService.Processor<>(new PersonServiceImpl()); + + arg.protocolFactory(new TCompactProtocol.Factory()); + arg.transportFactory(new TFramedTransport.Factory()); + arg.processorFactory(new TProcessorFactory(processor)); + + TServer server = new THsHaServer(arg); + System.out.println("Thrift service Started!"); + // 开启死循环 + server.serve(); +} +{% endcode %} + + + +{% code lang:java %} +public static void main(String[] args) { + TTransport transport = new TFramedTransport(new TSocket("localhost", 9090), 600); + TProtocol protocol = new TCompactProtocol(transport); + PersonService.Client client = new PersonService.Client(protocol); + + try { + transport.open(); + // 调用定义通过用户名获取用户信息的接口方法 getPersonByUsername + Person person = client.getPersonByUsername("张三"); + System.out.println(person.getUsername()); + System.out.println(person.getAge()); + System.out.println(person.isMarried()); + + System.out.println("-----------------------------"); + + // 调用定义保存用户信息的方法 + Person per = new Person(); + per.setUsername("李四"); + per.setAge(30); + per.setMarried(true); + client.savePerson(per); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } finally { + transport.close(); + } +} +{% endcode %} + + +{% endtabs %} + +>相关源码[rc-cluster-netty](https://github.com/RootCluster/rc-cluster-netty/tree/master/src/main/java/org/incoder/thrift) + +## 其他 + +本地 Thrift 的 Python 环境,需要下载官方的文件,进行安装 + +```bash +# 方式一: +pip3 install thrift +# 方式二,下载官方包,进行安装 +# 1. 下载文件 +curl https://mirrors.tuna.tsinghua.edu.cn/apache/thrift/0.13.0/thrift-0.13.0.tar.gz +# 2. 解压文件 +tar -zvxf thrift-0.13.0.tar.gz +# 3. 安装thrift +cd thrift-0.13.0/lib/py/ +sudo python setup.py install +``` \ No newline at end of file diff --git a/source/_posts/netty.md b/source/_posts/netty.md new file mode 100644 index 000000000..b37d165d6 --- /dev/null +++ b/source/_posts/netty.md @@ -0,0 +1,95 @@ +--- +title: Netty初体验(一) +date: 2019-11-20 10:32:10 +categories: Netty +tag: [Netty] +--- + +[Netty](https://netty.io) 是国内外各大互联网公司的必备网络应用框架,Netty 主要处理与网络相关的一些应用。由于 Netty 设计的巧妙的实现方式,以及对协议很好的实现,使的 Netty 可以在各种应用场景下广泛的应用,无论是传统基于HTTP协议的访问方式,还是更底层基于socket的访问方式,以及支持HTML5规范中的websocket的长连接特性,都提供了比较好的支持 + + + +Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients(Netty 是一个异步的,事件驱动的网络应用框架,是可维护的,高性能的,协议化的服务端和客户端快速开发方式) + +Netty 是一个非阻塞(NIO)客户端服务端框架,它可以快速的进行网络应用开发,例如:基于协议的客户端和服务端。它极大的简化并且支持流式的网络程序,例如:基于TCP和UDP的socket服务 +'快捷方便'并不意味着最终的应用程序会受到可维护性或性能问题的影响。Netty经过精心设计,具有实施许多协议所获得的经验,如FTP,SMTP,HTTP以及各种基于二进制和文本的遗留协议。因此,Netty 成功地找到了一种在不妥协的情况下实现易于开发,性能,稳定性和灵活性的方法 + +## 特点 + +* 适用于各种传输类型统一API - 阻塞和非阻塞 socket +* 基于灵活且可扩展的事件模型,可以清晰地关注分离(separation of concerns) +* 高度可定制的线程模型 - 单线程,一个或多个线程池,如:Staged Event Driven Architecture(SEDA,阶段型事件驱动架构,将一个请求分成若干个阶段,每个阶段可以根据自身情况不用数量的线程来分别进行处理,阶段与阶段之间是通过事件驱动的这种异步通讯模式来进行沟通及通信) +* 真正的无连接数据报套socket支持(since 3.1) + +## 性能 + +* 更高的吞吐量,更低的延迟 +* 减少资源消耗 +* 不必要的内存复制降到最低 + +## 安全 + +* 完整的SSL / TLS 和 StartTLS 支持 + +## Netty 使用场景 + +1. 作为HTTP的服务器,类似与Jetty,Tomcat这种Servlet容器,只是Netty在充当HTTP的服务器时,它采用的编程模型并不是基于Servlet的规范,原因是Netty并没有实现Servlet的接口,Servlet的实现,Netty有自己的实现方式 +2. 作为RPC通讯的框架,通讯的协议(可自定义),通讯的库,实现远程过程的调用,基于Socket方式(广泛使用) +3. 作为长连接的服务器,基于WebSocket,实现客户端和服务端之间的长连接通信 + +环境说明 +* System:macOS +* Java:JDK1.8+ +* Netty:4.1.25.Final + +### HTTP + +Netty 基于Servlet的规范,是一种特定的方式(更底层),因此 Netty 更专注于底层的性能等方面,所以在应用层开发时,是由开发人员自行去组装对请求,对 **请求路由** 处理等 + +> 示例:[HTTP](https://github.com/RootCluster/rc-cluster-netty/tree/master/src/main/java/org/incoder/netty/http) + +### Socket + +Socket 是计算机网络中用于在节点内发送或接收数据的内部端点。具体来说,它是网络软件 (协议栈) 中这个端点的一种表示,包含通信协议、目标地址、状态等,是系统资源的一种形式 + +> 示例:[Socket](https://github.com/RootCluster/rc-cluster-netty/tree/master/src/main/java/org/incoder/netty/socket) + +### Websocket + +WebSocket 是HTML5 规范的一部分,也是基于 HTTP 协议之上的一种协议,WebSocket主要是解决 HTTP 上存在的一些问题; +1. HTTP 一种无状态(同一客户端发出的第一次请求接收到响应后,客户端发送第二次请求,这两次请求之间没有任何关联)的协议,HTTP 无法追踪某一请求来自哪一个客户端,客户端之前在服务器上存在一些信息(常见的解决方式:cookie,session) +2. HTTP 是基于请求响应模式的协议,请求的发起方一定是客户端,服务器将响应返回给客户端后连接就断掉了(HTTP 1.0),在 1.0 的基础上连接可以短时间的保持,一种 keep-alive 机制(HTTP 1.1) + +通常我们所使用的长连接技术 +* 早期采用轮询的方式保持与服务器的连接 +* 目前通常采用 Websocket 的连接方式保持与服务器的连接 + * 客户端(浏览器)与服务器建立连接后,没有其他因素干扰,连接是不会断,一直存在,客户端与服务器双方是对等的,不再区分谁是客户端,谁是服务端,客户端可以发送数据给服务端,服务端也可以发送数据给客户端,在真正意义上实现了服务端的推技术 + * 长连接在建立初期会发送带有 header 头信息的网络请求,在连接建立后,在长连接之上只需要发送需要传递的数据(真正的数据)即可 + * Websocket 是基于 HTTP 协议 + * Websocket 也可以用于非浏览器的场景,只要你的库支持 Websocket 即可 + +> 示例: +> * [Websocket Server](https://github.com/RootCluster/rc-cluster-netty/tree/master/src/main/java/org/incoder/netty/websocket) +> * [Websocket Client](https://github.com/RootCluster/rc-cluster-netty/blob/master/src/webapp) + +### Heartbeat + +对于服务器上的集群服务(zookeeper 或者其他的应用服务),或者是客户端与服务端之间的长连接,需要一种机制来检测客户端还是 alive,这种机制就是 heartbeat +* 对于服务器上的这些服务与服务之间,节点与节点之间的通信(无一例外都使用 TCP 连接通信),节点之间的通信如何保证(A 节点感知到 B 或者其他节点是未宕机),此时就需要心跳来检测对应的服务或节点还是正常的 +* 对于客户端与服务器之间由于网络问题,或者客户端开启飞行模式,或者关机等状态,服务端是无法感知,因此也需要借助心跳来检测客户端是否关机或开启了飞行模式 + +> 示例:[Heartbeat](https://github.com/RootCluster/rc-cluster-netty/tree/master/src/main/java/org/incoder/netty/heartbeat) + +## 总结 + +### Netty 程序编写步骤 + +1. 定义好父子的(bossGroup:获取链接,workerGroup:真正来处理链接)线程组(EventLoopGroup),服务器启动时关联一个处理器处理器类似Initializer这样的处理器 +2. Initializer定义好自定义的或Netty本身提供的ChannelHandler通道处理器,在initChannel中自定义添加若干个处理器 +3. 实现自定义处理器ChannelHandler中特定的回调方法 + +### Netty 程序测试 +1. 启动 Server 服务 +2. 访问启动的服务,使用 curl命令 或者使用浏览器访问进行访问 + +![netty-test](https://res.cloudinary.com/incoder/image/upload/v1575207499/blog/netty-http.png) \ No newline at end of file diff --git a/source/_posts/network-http.md b/source/_posts/network-http.md new file mode 100644 index 000000000..936e5e0a5 --- /dev/null +++ b/source/_posts/network-http.md @@ -0,0 +1,207 @@ +--- +title: Http VS Https +date: 2018-06-22 01:14:25 +categories: Network +tag: [Http,Https] +--- + +## 基础名称 + +### 请求报文 + +客户端发送一个HTTP请求到服务器的请求消息包括以下格式: +请求行(request line)、请求头(header)、请求内容组成,如下请求报文的一般格式。 +![请求报文](https://res.cloudinary.com/incoder/image/upload/v1562212437/blog/newwork-request.webp) + + + +#### 请求行 + +1. 方法: + * GET: 获取资源 + * POST: 向服务器端发送数据,传输实体主体 + * PUT: 传输文件 + * HEAD: 获取报文首部 + * DELETE: 删除文件 + * OPTIONS: 询问支持的方法 + * TRACE: 追踪路径 +2. URL: + `scheme://host:port/path?query` + * scheme: 表示协议,如Http, Https, Ftp等 + * host: 表示所访问资源所在的主机名:如:www.baidu.com + * port: 表示端口号,Http默认为80,Https默认为443 + * path: 表示所访问的资源在目标主机上的储存路径 + * query: 表示查询条件 + +3. 协议/版本号: + +#### 请求头 + +1. 通用首部(General Header) +2. 请求首部(Request Header) +3. 实体首部(Entity Header Fields) + +#### 请求内容 + +如: 客户端POST的数据就放在这里(对比:GET的数据放在请求行的URL里) + +例如: +![请求示例](https://res.cloudinary.com/incoder/image/upload/v1529805798/blog/http-request.png) + +### 响应报文 + +服务端响应一个HTTP请求消息包括以下格式: +响应行(response line)、响应头(header)、响应内容组成 + +#### 响应行 + +1. 状态码: + * 1XX:Informational(信息性状态码) + * 2XX:Success(成功状态码) + * 3XX:Redirection(重定向) + * 4XX:Client Error(客户端错误状态码) + * 5XX:Server Error(服务器错误状态吗) +2. 状态码描述: +3. 协议/版本号: + +#### 响应头 + +1. 通用首部(General Header) +2. 响应首部(Response Header) +3. 实体首部(Entity Header Fields) + +#### 响应内容 + +如:服务器返回的HTML、JSON等数据 + +![响应示例](https://res.cloudinary.com/incoder/image/upload/v1529805798/blog/http-response.png) + +## Http + +### 概念 + +* [HTTP](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE):超文本传输协议(HyperText Transfer Protocol)是一种用于分布式、协作式和超媒体信息系统的应用层协议. +* HTTP是万维网的数据通信的基础. + +### 通信 + +1. 建立TCP连接 +在HTTP工作开始之前,Client首先要通过网络与Service建立连接,该连接是通过TCP来完成的,HTTP是比TCP更高层次的应用层协议,根据规则,只有低层协议建立之后才能进行更高层协议的连接,因此,首先要建立TCP连接 +2. Client发起HTTP请求(Request) +Requset通常包含请求行,请求头,请求内容这三部风组成的请求报文 +3. Service发送HTTP响应(Response) +Response通常包含响应行,响应头,响应内容这三部风组成的响应报文 +4. Client关闭TCP连接 + +### 特点 + +1. 无状态 + * 每个请求结束后都会被关闭,每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况 + * 服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器 +2. 明文传输,可能被窃听 +3. 不验证通信方的身份,可能遭遇伪装 + * HTTP 协议中的请求和响应不会对通信方进行确认。也就是说存在“服务器是否就是发送请求中 URI 真正指定的主机,返回的响应是否真的返回到实际提出请求的客户端”等类似问题 + * HTTP 协议通信时,由于不存在确认通信方的处理步骤,任何人都可以发起请求 +4. 无法证明报文的完整性,可能遭遇篡改 + * 在请求或响应送出之后直到对方接收之前的这段时间内,即使请求或响应的内容遭到篡改,也没有办法获悉 + +## Https + +### 概念 + +* [HTTPS](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE):超文本传输安全协议(Hypertext Transfer Protocol Secure,常称为HTTP over TLS,HTTP over SSL或HTTP Secure)是一种通过计算机网络进行安全通信的传输协议. +* HTTPS经由HTTP进行通信,但利用`SSL/TLS`来加密数据包. + +> HTTP+加密+认证+完整性保护 = HTTPS + +![HTTP VS HTTPS](https://res.cloudinary.com/incoder/image/upload/v1529822573/blog/HTTPS.png) + +### 通信 + +#### SSL/TLS + +SSL/TLS:安全传输层协议(Transport Layer Security), 是介于TCP和HTTP之间的一层安全协议,TLS的前身是SSL(Secure Sockets Layer) + +>TLS/SSL关系 +* SSL2.0 +* SSL3.0 +* TLS1.0(SSL3.1) +* TLS1.1(SSL3.2) +* TLS1.2(SSL3.3) + +#### SSL/TLS工作原理 + +HTTPS协议的主要功能都依赖于SSL/TLS协议,SSL/TLS的功能实现主要依赖于三类算法:`对称加密`,`非对称加密`,`散列函数Hash` +* 非对称加密实现身份认证和密钥协商, +* 对称加密算法采用协商的密钥对数据加密, +* 基于散列函数验证信息的完整性 + +#### SSL/TLS协议实现 + +TLS以记录协议(record protocol)实现。记录协议负责在传输连接上交换所有的底层消息,并可以配置加密。每一条TLS记录以一个短标头起始。标头包含记录内容的类型(或子协议)、协议版本和长度 + +TLS的主规格说明书定义了四个核心子协议: + +* 握手协议(handshake protocol); +* 密钥规格变更协议(change cipher spec protocol); +* 应用数据协议(application data protocol); +* 警报协议(alert protocol); + +#### 握手协议 + +握手是TLS协议中最精密复杂的部分。在这个过程中,通信双方协商连接参数,并且完成身份验证。根据使用的功能的不同,整个过程通常需要交换6~10条消息。根据配置和支持的协议扩展的不同,交换过程可能有许多变种,在使用中经常可以观察到以下三种流程: +* 单向验证(完整的握手,对服务器进行身份验证) +* 双向验证(对客户端和服务器都进行身份验证的握手) +* 简短握手(恢复之前的会话) + +##### 单向验证 + +![单向验证](https://blog-10039692.file.myqcloud.com/1494841223417_6503_1494841223715.png) +1. Handshake:ClentHello +客户端通过发送 Client Hello 报文开始 SSL通信。报文中包含客户端支持的 SSL的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。 +2. Handshake:ServerHello +服务器可进行 SSL通信时,会以 ServerHello 报文作为应答。和客户端一样,在报文中包含 SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。 +3. Handshake:Certificate +之后服务器发送 Certificate 报文。报文中包含公开密钥证书。 +4. Handshake:ServerHelloDone +最后服务器发送 ServerHelloDone 报文通知客户端,最初阶段的 SSL握手协商部分结束。 +5. Handshake:ClientKeyExchange +SSL第一次握手结束之后,客户端以 ClientKeyExchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-mastersecret 的随机密码串。该报文已用3 中的公开密钥进行加密。 +6. ChangeCipherSpec +接着客户端继续发送 ChangeCipherSpec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。 +7. Handshake:Finished +客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。 +8. ChangeCipherSpec +服务器同样发送 ChangeCipherSpec 报文。 +9. Handshake:Finished +服务器同样发送 Finished 报文。 +10. Application Data(HTTP) +服务器和客户端的 Finished 报文交换完毕之后,SSL连接就算建立完成。当然,通信会受到 SSL的保护。从此处开始进行应用层协议的通信,即发送 HTTP 请求。 +11. Application Data(HTTP) +应用层协议通信,即发送 HTTP 响应。 +12. Alert:warning,close notify +最后由客户端断开连接。断开连接时,发送 close_notify 报文(上图做了一些省略,实际到这一步还需要发送TCP FIN报文关闭TCP链接) + +##### 双向验证 + +![双向验证](https://blog-10039692.file.myqcloud.com/1494841503771_6933_1494841504095.png) +同单向验证流程相比,双向验证多了如下两条消息:`CertificateRequest`与`CertificateVerify`,其余流程大致相同 +* CertificateRequest +CertificateRequest是TLS规定的一个可选功能,用于服务器认证客户端的身份。通过服务器要求客户端发送一个证书实现,服务器应该在ServerKeyExchange之后立即发送CertificateRequest消息 +* CertificateVerify +当需要做客户端认证时,客户端发送CertificateVerify消息,来证明自己确实拥有客户端证书的私钥。这条消息仅仅在客户端证书有签名能力的情况下发送 + +#### 应用数据协议(application data protocol) + +应用数据协议携带着应用消息,只以TLS的角度考虑的话,这些就是数据缓冲区。记录层使用当前连接安全参数对这些消息进行打包、碎片整理和加密 + +#### 警报协议(alert protocol) + +警报的目的是以简单的通知机制告知对端通信出现异常状况。它通常会携带close_notify异常,在连接关闭时使用,报告错误 + +## 附录 + +* 《图解HTTP》 +* [HTTP | MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Overview) +* [数字证书及CA的扫盲介绍](https://kb.cnblogs.com/page/194742) +* [HTTPS 原理浅析及其在 Android 中的使用](https://cloud.tencent.com/developer/article/1005073) \ No newline at end of file diff --git a/source/_posts/network-okhttp1.md b/source/_posts/network-okhttp1.md new file mode 100644 index 000000000..ad6d0c1b5 --- /dev/null +++ b/source/_posts/network-okhttp1.md @@ -0,0 +1,544 @@ +--- +title: Network(一) 之OkHttp 入门 +date: 2018-06-23 12:44:25 +categories: Network +tag: OkHttp +--- + +自从Android4.4的源码中可以看到`HttpURLConnection`已经替换成`OkHttp`开始( [JakeWharton曾在Twitter表示](https://twitter.com/JakeWharton/status/482563299511250944) ) ,`OkHttp`+`Retrofit`+`RxJava`的组合网络请求一直经久不衰,主流app的网络架构基本都是这样的组合模式,存在即合理,说明`OkHttp`+`Retrofit`+`RxJava`的方式确实给开发,用户体验等带来可观的优势,那么这个系列文章围绕Android的网络展开. + +OkHttp:An HTTP & HTTP/2 client for Android and Java applications + + + +>Android 历史网络库 +* `HttpClient` 是 Apache 提供的HTTP网络访问接口,从一开始的时候就被引入到了Android的API中; +* `HttpURLConnection` 是一种多用途, 轻量极的HTTP客户端, 提供的API比较简单, 可以容易地去使用和扩展. + +## OkHttp优势 +* 支持HTTP/2, HTTP/2通过使用多路复用技术在一个单独的TCP连接上支持并发, 通过在一个连接上一次性发送多个请求来发送或接收数据 +* 如果HTTP/2不可用, 连接池复用技术也可以极大减少延时 +* 支持GZIP, 可以压缩下载体积 +* 响应缓存可以直接避免重复请求 +* 会从很多常用的连接问题中自动恢复 +* 如果您的服务器配置了多个IP地址, 当第一个IP连接失败的时候, OkHttp会自动尝试下一个IP +* OkHttp还处理了代理服务器问题和SSL握手失败问题,等等... + +## 基本使用 +该系列版本说明 +* OkHttp版本统一:**3.10.0** +* JDK:**1.8+** + +Gradle包导入 +```groovy +// okhttp核心库 +implementation 'com.squareup.okhttp3:okhttp:3.10.0' +// okhttp网络请求拦截日志库 +implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' +``` +> 关于网络请求 +基本网络请求由请求(`请求行`,`请求头`,`请求内容`),响应(`响应行`,`响应头`,`响应内容`)两大部分组成,具体的内容请查看[Http VS Https](https://incoder.org/2018/06/22/network-http)这篇文章 + +### OkHttp请求 +已在[Http VS Https](https://incoder.org/2018/06/22/network-http/#%E8%AF%B7%E6%B1%82%E6%8A%A5%E6%96%87)文章中介绍了,HTTP请求相关内容 + +### OkHttp响应 +已在[Http VS Https](https://incoder.org/2018/06/22/network-http/#%E5%93%8D%E5%BA%94%E6%8A%A5%E6%96%87)文章中介绍了,HTTP响应相关内容 + +## 同步与异步 +网络请求执行方式为:同步与异步;`同步`和`异步`关注的是消息通信机制 (synchronous communication/ asynchronous communication) + +### 同步 +就是在发出一个 **调用** 时,在没有得到结果之前,该 **调用** 就不返回,但是一旦调用返回,就得到返回值了。 +换句话说,就是由 **调用者** 主动等待这个 **调用** 的结果。 +Okhttp同步(`execute()`):Invokes the request immediately, and blocks until the response can be processed or is in error. + +```java +String url = "https://api.github.com/users/BladeCode"; +OkHttpClient client = new OkHttpClient(); + +String run(String url) throws IOException { + Request request = new Request.Builder().url(url).build(); + // 执行同步操作 + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + return response.body().string(); + } else { + throw new IOException("Unexpected code " + response); + } +} +``` + +### 异步 +**异步** 则与同步相反,**调用** 在发出之后,这个调用就直接返回了,所以没有返回结果。 +换句话说,当一个异步过程调用发出后,**调用者** 不会立刻得到结果。而是在 **调用** 发出后,**被调用者** 通过状态、通知来通知 **调用者**,或通过回调函数处理这个调用。 +Okhttp同步(`enqueue(Callback responseCallback)`):Schedules the request to be executed at some point in the future. + +```java +String url = "https://api.github.com/users/BladeCode"; +OkHttpClient client = new OkHttpClient(); + +Request request = new Request.Builder().url(url).build(); +// 返回response 对象 +Response response = client.newCall(request).enqueue(new Callback() { + + @Override + public void onFailure(Call call, IOException e) { + System.out.println(e.toString()); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + // 字符串形式表达响应 + System.out.println(response.body().string()); + // 或流的形式表达响应 + System.out.println(response.body().charStream()); + System.out.println(response.body().byteStream()); + } +}); +``` +>注意: +* 响应体太大(超过1MB), 应避免使用 string()方法, 因为它会将把整个文档加载到内存中. +* 对于超过1MB的响应body, 应使用流的方式来处理响应body. 这和我们处理xml文档的逻辑是一致的, 小文件可以载入内存树状解析, 大文件就必须流式解析 + +## OkHttp Get +```java +String url = "https://api.github.com/users/BladeCode"; +OkHttpClient client = new OkHttpClient(); + +String run(String url) throws IOException { + Request request = new Request.Builder().url(url).build(); + Response response = client.newCall(request).execute(); + + if (response.isSuccessful()) { + return response.body().string(); + } else { + throw new IOException("Unexpected code " + response); + } +} +``` + +## OkHttp Post +```java +public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + +OkHttpClient client = new OkHttpClient(); + +String post(String url, String json) throws IOException { + RequestBody body = RequestBody.create(JSON, json); + Request request = new Request.Builder() + .url(url) + .post(body) + .build(); + + Response response = client.newCall(request).execute(); + + if (response.isSuccessful()) { + return response.body().string(); + } else { + throw new IOException("Unexpected code " + response); + } + +} +``` + +### Posting a String +```java +public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8"); + +private final OkHttpClient client = new OkHttpClient(); + +public void run() throws Exception { + String postBody = "" + + "Releases\n" + + "--------\n" + + "\n" + + " * _1.0_ May 6, 2013\n" + + " * _1.1_ June 15, 2013\n" + + " * _1.2_ August 11, 2013\n"; + + Request request = new Request.Builder() + .url("https://api.github.com/markdown/raw") + .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody)) + .build(); + + try(Response response = client.newCall(request).execute()){ + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } +} +``` +> 注意:当提交数据大于1MB,请使用流的方式 + +### Post Streaming +```java +public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8"); + +private final OkHttpClient client = new OkHttpClient(); + +public void run() throws Exception { + RequestBody requestBody = new RequestBody() { + @Override + public MediaType contentType() { + return MEDIA_TYPE_MARKDOWN; + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + sink.writeUtf8("Numbers\n"); + sink.writeUtf8("-------\n"); + for (int i = 2; i <= 997; i++) { + sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i))); + } + } + + private String factor(int n) { + for (int i = 2; i < n; i++) { + int x = n / i; + if (x * i == n) return factor(x) + " × " + i; + } + return Integer.toString(n); + } + }; + + Request request = new Request.Builder() + .url("https://api.github.com/markdown/raw") + .post(requestBody) + .build(); + + try(Response response = client.newCall(request).execute()){ + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } +} +``` + +### Posting a File +```java +public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8"); + +private final OkHttpClient client = new OkHttpClient(); + +public void run() throws Exception { + File file = new File("README.md"); + + Request request = new Request.Builder() + .url("https://api.github.com/BladeCode/raw") + .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file)) + .build(); + + try(Response response = client.newCall(request).execute()){ + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } +} +``` + +### Posting form parameters +使用`FormEncodingBuilder`来构建和HTML标签相同效果的请求体。键值对将使用一种HTML兼容形式的URL编码来进行编码 + +```java +private final OkHttpClient client = new OkHttpClient(); + +public void run() throws Exception { + RequestBody formBody = new FormEncodingBuilder() + .add("search", "Jurassic Park") + .build(); + Request request = new Request.Builder() + .url("https://en.wikipedia.org/w/index.php") + .post(formBody) + .build(); + + try(Response response = client.newCall(request).execute()){ + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } +} +``` + +### Posting a multipart request +`MultipartBuilder`可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的`Content-Disposition`。如果`Content-Length`和`Content-Type`可用的话,他们会被自动添加到请求头中。 + +```java +private static final String IMGUR_CLIENT_ID = "..."; +private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png"); + +private final OkHttpClient client = new OkHttpClient(); + +public void run() throws Exception { + // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image + RequestBody requestBody = new MultipartBuilder() + .type(MultipartBuilder.FORM) + .addPart( + Headers.of("Content-Disposition", "form-data; name=\"title\""), + RequestBody.create(null, "Square Logo")) + .addPart( + Headers.of("Content-Disposition", "form-data; name=\"image\""), + RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png"))) + .build(); + + Request request = new Request.Builder() + .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID) + .url("https://api.imgur.com/3/image") + .post(requestBody) + .build(); + + try(Response response = client.newCall(request).execute()){ + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } +} +``` +## Header +通常,HTTP headers 的工作方式类似于 `Map`:每个字段都有一个值或没有,但是一些headers允许多个值 +* 例如:Guava's Multimap. +* 例如:提供多个vary headers的HTTP响应是合法且常见的。OkHttp的API试图使用两种情况都很舒适 + +在编写请求headers时 +* 使用 `header(name, value)`将 `name` 的唯一内容设置为 `value`。如果 `name` 存在现有值,则在添加新值之前将删除它。 +* 使用 `addHeader(name, value)` 添加 `headers` 不会删除已存在的 `header` + +在读取headers响应时,使用 `header(name)` 返回最后异常出现的命名值。通常这也是唯一发生,如果没有值,则 `header(name)` 返回null。将所有字段的值作为列表读取,请使用 `headers(name)` + +```java +private final OkHttpClient client = new OkHttpClient(); + +public void run() throws Exception { + Request request = new Request.Builder() + .url("https://api.github.com/repos/square/okhttp/issues") + .header("User-Agent", "OkHttp Headers.java") + .addHeader("Accept", "application/json; q=0.5") + .addHeader("Accept", "application/vnd.github.v3+json") + .build(); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println("Server: " + response.header("Server")); + System.out.println("Date: " + response.header("Date")); + System.out.println("Vary: " + response.headers("Vary")); + } +} +``` + +### Response Caching +实现缓存响应,你需要一个可以读写的缓存目录,以及缓存大小的限制。缓存目录应该是私有的,不受信任的应用程序不应该读取其内容 + +让多个缓存同时访问同一缓存目录是错误的。大多数应用程序应该只调用一次 `new OkHttpClient()`,使用它们的缓存配置它,并在任何地方使用相同的实例。否则,两个缓存实例将互相踩踏,破坏响应缓存,并可能导致程序奔溃 + +响应缓存使用HTTP headers进行所有的配置。你可以添加headers,如:`Cache-Control: max-stale=3600`,OkHttp的缓存将遵循它。你的Web服务器使用自己的响应headers配置缓存响应的时间,例如:`Cache-Control: max-age=9600`。有缓存headers可强制缓存响应,强制网络响应,或者强制使用条件GET验证网络响应 + +```java +private final OkHttpClient client; + +public CacheResponse(File cacheDirectory) throws Exception { + // 设置缓存大小 10 MiB + int cacheSize = 10 * 1024 * 1024; + // 实例化Cache对象 + Cache cache = new Cache(cacheDirectory, cacheSize); + + client = new OkHttpClient.Builder() + .cache(cache) + .build(); +} + +public void run() throws Exception { + Request request = new Request.Builder() + .url("http://publicobject.com/helloworld.txt") + .build(); + + String response1Body; + try (Response response1 = client.newCall(request).execute()) { + if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1); + + response1Body = response1.body().string(); + System.out.println("Response 1 response: " + response1); + System.out.println("Response 1 cache response: " + response1.cacheResponse()); + System.out.println("Response 1 network response: " + response1.networkResponse()); + } + + String response2Body; + try (Response response2 = client.newCall(request).execute()) { + if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2); + + response2Body = response2.body().string(); + System.out.println("Response 2 response: " + response2); + System.out.println("Response 2 cache response: " + response2.cacheResponse()); + System.out.println("Response 2 network response: " + response2.networkResponse()); + } + + System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body)); +} +``` +* 要阻止响应使用缓存,请使用 `CacheControl.FORCE_NETWORK` +* 要阻止它使用网络,请使用 `CacheControl.FORCE_CACHE` + +>警告:如果你使用 `FORCE_CACHE` 且响应需要网络,OkHttp将返回504不满意请求响应 + +### Canceling a Call +使用 `Call.cancel()` 立即停止正在进行的请求,如果线程当前正在请求或读取响应,则它将收到 `IOException`。当不在需要call时,使用它来保护网络,例如,当你的用户导航离开应用程序时,同步和异步调用都可以取消 + +```java +private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); +private final OkHttpClient client = new OkHttpClient(); + +public void run() throws Exception { + Request request = new Request.Builder() + .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay. + .build(); + + final long startNanos = System.nanoTime(); + final Call call = client.newCall(request); + + // Schedule a job to cancel the call in 1 second. + executor.schedule(new Runnable() { + @Override public void run() { + System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f); + call.cancel(); + System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f); + } + }, 1, TimeUnit.SECONDS); + + System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f); + try (Response response = call.execute()) { + System.out.printf("%.2f Call was expected to fail, but completed: %s%n", + (System.nanoTime() - startNanos) / 1e9f, response); + } catch (IOException e) { + System.out.printf("%.2f Call failed as expected: %s%n", + (System.nanoTime() - startNanos) / 1e9f, e); + } +} +``` + +### Timeouts +当无法访问时,使用超时来使call失败。网络分区可能是由于客户端连接问题,服务器可读性问题或其他任何问题时。OkHttp支持连接,读取和写入超时配置 + +```java +private final OkHttpClient client; + +public ConfigureTimeouts() throws Exception { + client = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build(); +} + +public void run() throws Exception { + Request request = new Request.Builder() + .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay. + .build(); + + try (Response response = client.newCall(request).execute()) { + System.out.println("Response completed: " + response); + } +} +``` + +### Per-call Configuration +所有的HTTP 客户端配置都在 `OkHttpClient` 中,包括代理设置,超时和缓存。当你需要修改单个调用时的配置时,请调用 `OkHttpClient.newBuilder()`。这将返回与原始客户端共享相同连接池,调度程序和配置的构建器(`Builder`) + +```java +// 示例:我们发出一个请求,其中500毫秒超时,另一个请求超时3000毫秒 +private final OkHttpClient client = new OkHttpClient(); + +public void run() throws Exception { + Request request = new Request.Builder() + // This URL is served with a 1 second delay. + .url("http://httpbin.org/delay/1") + .build(); + + // Copy to customize OkHttp for this request. + OkHttpClient client1 = client.newBuilder() + .readTimeout(500, TimeUnit.MILLISECONDS) + .build(); + try (Response response = client1.newCall(request).execute()) { + System.out.println("Response 1 succeeded: " + response); + } catch (IOException e) { + System.out.println("Response 1 failed: " + e); + } + + // Copy to customize OkHttp for this request. + OkHttpClient client2 = client.newBuilder() + .readTimeout(3000, TimeUnit.MILLISECONDS) + .build(); + try (Response response = client2.newCall(request).execute()) { + System.out.println("Response 2 succeeded: " + response); + } catch (IOException e) { + System.out.println("Response 2 failed: " + e); + } +} +``` + +### Handling authentication +OkHttp可以自动重试未经身份验证的请求。如果响应为401 Not Authorized,则要求Authenticator提供凭证。实现应该构建一个包含缺少凭证的新请求。如果没有可用的凭证,则返回null以跳过重试。 + +使用 `Response.challenges()`来获取任何身份验证挑战的方案和领域。在完成基本挑战时,使用 `Credentials.basic(username, password)` 对请求header进行编码 + +```java +private final OkHttpClient client; + +public Authenticate() { + client = new OkHttpClient.Builder() + .authenticator(new Authenticator() { + @Override public Request authenticate(Route route, Response response) throws IOException { + if (response.request().header("Authorization") != null) { + return null; // Give up, we've already attempted to authenticate. + } + + System.out.println("Authenticating for response: " + response); + System.out.println("Challenges: " + response.challenges()); + String credential = Credentials.basic("jesse", "password1"); + return response.request().newBuilder() + .header("Authorization", credential) + .build(); + } + }) + .build(); +} + +public void run() throws Exception { + Request request = new Request.Builder() + .url("http://publicobject.com/secrets/hellosecret.txt") + .build(); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } +} +``` + +为避免在身份验证不起作用时进行多次重试,你可以在返回null以放弃,例如,你可能希望在尝试这些确切凭证时跳过重试 +```java +if (credential.equals(response.request().header("Authorization"))) { + return null; // If we already failed with these credentials, don't retry. +} +``` + +当你达到应用程序定义的尝试限制时,你也可以跳过重试 +```java +private int responseCount(Response response) { + int result = 1; + while ((response = response.priorResponse()) != null) { + result++; + } + return result; +} + +if (responseCount(response) >= 3) { + // If we've failed 3 times, give up. + return null; +} +``` + +## 附录 +* [OkHttp Wiki](https://github.com/square/okhttp/wiki) +* [怎样理解阻塞非阻塞与同步异步的区别](https://www.zhihu.com/question/19732473) +* [OkHttp使用教程](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0106/2275.html) \ No newline at end of file diff --git a/source/_posts/ooad-uml.md b/source/_posts/ooad-uml.md new file mode 100644 index 000000000..89a4d510b --- /dev/null +++ b/source/_posts/ooad-uml.md @@ -0,0 +1,206 @@ +--- +title: OOAD 与 UML +date: 2019-05-27 22:00:10 +categories: DevTool +tag: [OOAD, UML] +--- + +OOAD(Object Oriented Analysis and Desigin) 是根据 OO 的方法学,对软件系统进行分析和设计的过程 +* OOA(Object Oriented Analysis):分析阶段 +* OOD(Object Oriented Desigin):设计阶段 + +## What to do +分析阶段主要解决以下问题 +* 建立针对业务问题域的清晰视图 +* 列出系统必须要完成的核心任务 +* 针对问题域建立公共词汇表 +* 列出针对此问题域的最佳解决方案 + + + +## How to do +设计阶段主要解决以下问题(How to do?) +* 如何解决具体的业务问题 +* 引入系统工作所需的支持元素 +* 定义系统的实现策略 + +## OOP的主要特征 + +### 抽象(abstract) +* 忽略到一个对象或实体的细节而只关注其本质特征的过程 +* 简化功能与格式 +* 帮助用户与对象交互 + +### 封装(encapsulation) +* 隐藏数据和实现 +* 提供公共方法供用户调用功能 +* 对象的两种视图 + * 外部视图:对象能做的工作 + * 内部视图:对象如何完成工作 + +### 继承(inheritance) +* 通过存在的类型定义新类型的机制 +* 通常在两个类型之间存在“is a” 或 “kind of” 这样的关系 +* 通过继承可实现代码重用,另外继承也是多态的基础 +* 如苹果 “is a” 水果 + +### 多态(polymorphism) +* 一个名称,多种形式 +* 基于继承的多态 +* 调用方法时根据所给对象的不同选择不同的处理方式 +* 如 Football——play() +* 给出一个具体的足球或篮球,用户自动知道该使用谁的方式去执行 + +### 关联(association) +* 对象之间交互时的一种引用方式 +* 当一个对象通过对另一个对象的引用去使用另一个对象的服务或操作时,两个对象之间便产生了关联 +* 如 person 使用 computer,person 与 computer 之间就存在了关联关系 + +### 聚合(aggregation) +* 关联关系的一种,一个对象成为另一个对象的组成部分 +* 使用关系强的关联 +* 在两个对象之间存在 “has a”这样的关系,一个对象作为另一个对象的属性存在,在外部对象被产生时,可由客户端指定与其关联的内部对象 + +> 如汽车与轮胎,轮胎作为汽车的一个组成部分,它和汽车可由分别产生以后转配起来使用,但汽车可由换新轮胎,轮胎也可以卸下来给其他汽车使用 + +### 组合(composition) +* 当一个对象包含另一个对象时,外部对象负责管理内部对象的生命周期的情况 +* 关联关系中最为强烈的一种 +* 内部对象的创建由外部对象自己控制 +* 外部对象不存在时,内部对象也不能存在 + +> 如电视机与显示器 + +### 内聚与耦合(cohesion & coupling) +* 域模型是面向对象的。在面向对象术语中域模型也可称为设计模型。 +* 域模型由以下内容组成 + * 关联(Association):一对多,多对一,一对一 + * 依赖(Dependency) + * 聚集(Aggregation):整体和部分之间的关系 + * 一般化(泛化)(Generalization):类与类之间的**继承** +* 内聚:度量一个类独立完成某项工作的能力 +* 耦合:度量系统内或系统之间依赖关系的复杂度 +* 设计原则:增加内聚,减少耦合 + +## 开发过程概述 + +### 传统开发过程 +* 瀑布模型(真实环境,不可能满足这些) + +### 统一软件开发过程(USDP) +特点: 项目是迭代,递增 +* 迭代指生命周期中的一个步骤 +* 迭代导致“递增”或者是整个项目的增长 +* 大项目分解为子项目 +* 在每一个迭代的阶段,应该做以下工作 + * 选择并分析相关用例 + * 更加所选架构进行设计 + * 在组件层次实现设计 + * 验证组件满足用例的需要 +* 当一次迭代满足目标后,开发进入下一个迭代周期 +* 每一个周期包含一次或多次迭代 +* 一个阶段的结束称之为“里程碑” + +### 阶段 + +#### 初始化阶段 +该阶段的增量集中于: +* 项目启动 +* 建立业务模型 +* 定义业务问题域 +* 找出主要的风险因素 +* 定义项目需求的外延 +* 创建业务问题域的相关说明文档 + +#### 细化阶段 +本阶段的增量集中于 +* 高层的分析与设计 +* 建立项目的基础框架 +* 监督主要的风险因素 +* 制订达成项目目标的创建计划 + +#### 构建阶段 + 本阶段的增量集中于 + * 代码及功能的实现 + +#### 移交阶段 +本阶段的增量集中于: +* 向用户发布产品 +* beta 测试 +* 执行性能调优,用户培训和接收测试 + +#### 阶段特点 +每一个阶段所包含的工作流,每一次递增都由 5 个部分工作流组成 +* 需求与初始化分析 +* 分析 +* 设计 +* 实现 +* 测试 +* 每一次迭代执行工作流的深度不同 +* 早期的迭代在深度上覆盖初始工作流,后期迭代在深度上覆盖后期工作流 +* 80/20原则 + +## UML +UML(Unified Modeling Language)统一建模语言,图形化语言表示,它可以帮助我们在 OOAD 过程中标识元素,构建模块,分析过程并可通过文档说明系统中的重要细节 + +### 静态模型(static model) +* 创建并记录一个系统的静态特征 +* 反映一个软件系统基础,固定的框架结构 +* 创建相关问题域主要元素的视图 +* 静态建模包括: + * **用例图(use case diagrams)** + * **类图(class diagrams)** + * 对象图(object diagrams) + * 组件图(component diagrams) + * 部署图(deployment diagrams) + +### 动态模型(dynamic model) +* 用以展示系统的行为 +* 动态建模包括: + * **时序图(sequence diagrams)** + * 协作图(collaboration diagrams) + * 状态图(state chart diagrams) + * 活动图(activity diagrams) + +### UML 其他重要元素 +* 包(package) +* UML 的扩展机制 + * 注释(comments) + * 构造型(stereotypes) + * 标记值(tagged values) + * 限制(constraints) + +### 示例 + +#### 用例图 +* 展示系统的核心功能及逾期交互的用户 +* 用户被称为“活动者”(Actor) +* 用例使用椭圆表示 +* 为简化建模过程,用例图可以标注优先级 +![uml-usecase-diagram](https://res.cloudinary.com/incoder/image/upload/v1559486362/blog/uml-usecase-diagram.png) + +#### 类图 +* 表现类的特征 +* 类图描述了多个类,接口的特征,以及对象之间的协作与交互 +* 由一个或者多个矩形区域构成,内容包括 + * 类型(类名) + * 属性(可选) + * 操作(可选) +![uml-class-diagram](https://res.cloudinary.com/incoder/image/upload/v1559486361/blog/uml-class-diagram.png) + +#### 对象图 +* 表现对象的特征 +* 对象图展现了多个对象的特征及对象之间的交互 +![uml-object-diagram](https://res.cloudinary.com/incoder/image/upload/v1559486360/blog/uml-object-diagram.png) + +#### 组件图 +* 表示软件组件之间的关系 +![uml-component-diagram](https://res.cloudinary.com/incoder/image/upload/v1559486358/blog/uml-component-diagram.png) + +#### 部署图 +* 表现用于部署软件应用的物理设备信息 +![uml-deployment-diagram](https://res.cloudinary.com/incoder/image/upload/v1559486362/blog/uml-deployment-diagram.png) + +#### 时序图 +* 捕捉一段时间范围内多个对象之间的交互信息 +* 强调消息交互的时间顺序 diff --git a/source/_posts/open-license.md b/source/_posts/open-license.md new file mode 100644 index 000000000..67b8a73d8 --- /dev/null +++ b/source/_posts/open-license.md @@ -0,0 +1,51 @@ +--- +title: 开源协议,该如何选择 +date: 2020-11-25 22:30:10 +categories: Open Source +tag: License +--- + +现如今软件行业的发展完全离不开开源社区,很多优秀的软件应用、技术都能看到开源软件的影子,我们都是站在巨人的肩膀上。对于软件行业的从业者,能为开源项目贡献自己的力量,或是将自己对某一个细分领域所做的研究实践开源出来,这是一件非常值得骄傲的事情。而要参与一个大型的开源项目,你除了需要该项目涉及的核心技术知识外,还需要了解一定的开源项目运转方式等,对于如何参与开源项目,这里暂不做过多的介绍,有兴趣的可以移步 Gitee 发起的《[开源指北](https://gitee.com/gitee-community/opensource-guide)》项目,该项目中详细介绍了如何参与开源项目。本篇文章也不啰嗦这一点,仅仅围绕开源协议,我们应该清楚的常识和注意的点 + + + +在软件开发中**通常**有两种情况我们需要考虑软件的开源协议或者使用协议 +1. 我们需要使用到业界的一些优秀的软件包来提高我们开发的效率,避免了重复早轮子,所选择的这些软件包我们不但考虑功能的同时,也要考虑软件包的授权协议 +2. 我们需要将自己的经验或者软件产品需要开源时,为了保护自己的权益,我们也需要选择一个合适的开源协议 + +我这里还是引用比较经典 [阮一峰](http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html) 文章中所绘制关于如何选择开源协议的图 +![](http://www.ruanyifeng.com/blogimg/asset/201105/free_software_licenses.png) +图中已经很清楚的表示了如何去选择 [LGPL](https://baike.baidu.com/item/LGPL), [Mozilla](https://www.mozilla.org/en-US/MPL/), [GPL](), [BSD](), [MIT](), [Apache]() 这 6 种协议 + +## 常用协议 + +这里我们通过表格的形式介绍下这 6 种协议,当然除了表中列出的这些协议之外还有很多协议,我们就挨个来简单对他们有一个了解和认识 + +![](https://res.cloudinary.com/incoder/image/upload/v1609258585/blog/license.png) + +## 其他协议 + +### BY-NC-SA + +你会发现每篇文章下面都有申明版权,这里使用的是 [ BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/) 4.0 的协议,他们的含义如下 +* :知识共享(CreativeCommons) +* NC:非商业性使用(NonCommercial),您不得将本作品用于商业目的 +* SA:相同方式共享(ShareAlike),如果您再混合、转换或者基于本作品进行创作,您必须基于与原先许可协议相同的许可协议 分发您贡献的作品 + +使用此协议,您可以自由地 +1. 共享 — 在任何媒介以任何形式复制、发行本作品 +2. 演绎 — 修改、转换或以本作品为基础进行创作 +>只要你遵守许可协议条款,许可人就无法收回你的这些权利 + +## 选择 + +上面说了那么多,有些协议并没有展开来说可能并不适用你当前的所需要选择的协议,那么你可以根据实际情况去筛选,可通过 https://choosealicense.com, https://kaiyuanshe.cn/license-tool 这两个网站按照步骤去选择,最终确定协议即可 + +## 参考 + +1. [开源许可证选择器](https://kaiyuanshe.cn/license-tool/) +2. [Choose an open source license](https://choosealicense.com/) +3. [博云违反 Apache 2.0 开源协议被要求整改,开源协议到底应该如何遵守?](https://segmentfault.com/a/1190000022973105) +4. [开源协议是什么?有哪些?如何选择?](http://c.biancheng.net/view/2947.html) +5. [如何为你的代码选择一个开源协议](https://www.cnblogs.com/Wayou/p/how_to_choose_a_license.html) +6. [开源指北 Gitee](https://gitee.com/gitee-community/opensource-guide) \ No newline at end of file diff --git a/source/_posts/oss.md b/source/_posts/oss.md new file mode 100644 index 000000000..56bb029eb --- /dev/null +++ b/source/_posts/oss.md @@ -0,0 +1,170 @@ +--- +title: OSS 初体验 +date: 2021-03-27 09:44:46 +categories: OSS +tag: [OSS] +--- + +在之前 [SpringBoot(十二)文件上传](https://incoder.org/2021/03/10/springboot12) 文章中,已经学习了使用 SpringBoot 基础的功能,完成静态资源的管理,本片文章我们同样也是对非结构化的静态数据进行管理,不过这次我们使用的是比较常用的 OSS 服务,废话不说,我们一起开始 OSS 之旅吧 + + + +## 什么是 OSS + +OSS 是一种面向海量数据规模的分布式存储服务,具有稳定,可靠,安全,低成本的特点。主要用来存储各种非结构化的数据,比如视频,图像,日志,文本文件等。OSS 服务提供标准的 RESTful API 接口,并提供一些常用语言的 SDK 包,方便开发者进行快速开发和二次处理 + +## 常用的 OSS + +市面上提供云服务的厂商有很多,这里以阿里云的 OSS 服务为主来,完成 OSS 相关的学习和实践 + +## 依赖 + +```xml + + + com.aliyun.oss + aliyun-sdk-oss + 3.11.1 + +``` + +## OSS 工具类 + +```java +/** + * OSS 文件上传 + * + * @author : Jerry xu + * @since : 2020/11/3 09:12 + */ +@Slf4j +public class OssUtils { + + // 访问域名 + private static final String ENDPOINT = "xxxxx"; + // 存储空间 + private static final String BUCKET_NAME = "xxxxx"; + //==================访问密钥================== + private static final String ACCESS_KEY_ID = "xxxxx"; + // 用户用于加密签名字符串和OSS用来验证签名字符串的密钥,必须保密 + private static final String ACCESS_KEY_SECRET = "xxxxx"; + + /** + * 通过文件上传图片 + * + * @param file 文件 + * @return 上传结果地址 + */ + public static String uploadFileByFile(File file) { +// // NIO 方式 +// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath()); +// // 其他方式 +// byte[] fileByte = Files.readAllBytes(Paths.get(file.getPath())); +// return uploadFileByByte(fileByte, file); + URL url; + String urlStr = null; + String[] split = new String[0]; + try { + OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET); + // 获取文件名 + String fileName = file.getName(); + ossClient.putObject(BUCKET_NAME, fileName, file); + // 设置过期时间 + url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, new Date(System.currentTimeMillis() + 3600 * 24 * 365 * 10)); + log.info("原始图片地址:{}", url); + urlStr = url.toString(); +// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath()); +// PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, "test", new ByteArrayInputStream(fileByte)); +// // 不设置过期时间 +// PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest); +// // 去除过期时间参数地址 +// split = urlStr.split("\\?"); + } catch (Exception e) { + log.warn(e.getMessage()); + } + return urlStr; + } + + /** + * 通过字节数组上传图片 + * + * @param binaryBytes 字节数组 + * @param fileName 文件名 + * @return 上传结果地址 + */ + public static String uploadFileByByte(byte[] binaryBytes, String fileName) { + InputStream inputStream = new ByteArrayInputStream(binaryBytes); + return uploadFileByInputStream(inputStream, fileName); + } + + /** + * 通过输入流上传图片 + * + * @param inputStream 输入流 + * @param fileName 文件名 + * @return 上传结果地址 + */ + public static String uploadFileByInputStream(InputStream inputStream, String fileName) { + URL url; + String urlStr = null; + String[] split = new String[0]; + try { + OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET); +// String fileName = UUID.randomUUID().toString() + ".jpeg"; + ossClient.putObject(BUCKET_NAME, fileName, inputStream); + // 设置过期时间 + url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, new Date(System.currentTimeMillis() + 3600 * 24 * 365 * 10)); + log.info("原始图片地址:{}", url); + urlStr = url.toString(); +// byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath()); +// PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, "test", new ByteArrayInputStream(fileByte)); +// // 不设置过期时间 +// PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest); +// // 去除过期时间参数地址 +// split = urlStr.split("\\?"); + } catch (Exception e) { + log.warn(e.getMessage()); + } + return urlStr; + } + +} +``` + +## 上传 + +这里我们写一个上传接口 + +```java +@ApiOperation("文件上传", notes = "支持多图上传") +@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) +public List uploadTest(@RequestParam("file") List file) { + List uploadList = new ArrayList<>(file.size()); + file.forEach(t -> { + try { + String url = OssUtils.uploadFileByInputStream(t.getInputStream(), t.getOriginalFilename()); + uploadList.add(url); + } catch (IOException e) { + e.printStackTrace(); + } + }); + return uploadList; +} +``` + +## 测试 + +不废话了,直接看图就好了 +![](https://res.cloudinary.com/incoder/image/upload/v1618848130/blog/oss-upload.png) + +## 问题 + +1. 对于上传获取到的文件地址是一个会过期的地址,并不是一个固定不变的地址,如上截图所示,我偷懒直接将地址链接出的相关参数删去,拿到了一个永久存储的访问连接地址。但这里需要注意,这需要在你的 OSS 管理后台去设置你的文件存储的过期策略。这里就不进行截图演示了(主要是我没有登录系统的账号密码,逃 ~) +2. 对于上传的文件我没有自定义文件名,这里有个问题是当用户上传 OSS 服务中已经存在的文件名的文件时,新上传的会覆盖旧文件,因此这个地方需要根据实际的业务场景选择合适的方式。在 OssUtils 工具类中我已经注释掉了将文件名重命名的代码,你可以在此处按照你的业务进行更改 +3. 第三个问题就是结合上面的两点的汇总方案,其实呢,对于一般的系统,这些静态资源就存永久的连接地址即可。但目前新的系统对用户的资料等也有了 "稍微" 高一点的保护,就是这些资源都是有时效性的,获取的地址就是我们上传拿到的原始地址,而我们存放在数据库中当然也不会是之前那种永久的连接地址,而是对应图片的一个唯一标识信息(可以是重命名后的文件名或者其他能够唯一标识资源你的字段),然后用户访问这些资源时,用存放在数据库中的唯一标识去 OSS 服务上查询对应的资源,然后加载这个地址去显示。 + +## 参考 + +1. [阿里云对象存储 OSS](https://help.aliyun.com/product/31815.html) +2. [对象存储 Kodo](https://www.qiniu.com/products/kodo) +3. [华为云对象存储服务 OBS](https://support.huaweicloud.com/obs/index.html) \ No newline at end of file diff --git a/source/_posts/play-maze.md b/source/_posts/play-maze.md new file mode 100644 index 000000000..97979ed84 --- /dev/null +++ b/source/_posts/play-maze.md @@ -0,0 +1,115 @@ +--- +title: 迷宫如意琳琅图籍 +date: 2020-12-10 09:44:46 +categories: Play +tag: [迷宫] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1611486360/blog/G2.png) + + + +故宫博物院出品,奥秘之家设计制作(曾推出线下实景地铁逃脱游戏,2018联合《唐人街探案 2》推出《侦探笔记》的互动解密游戏,以及配合电影开发Crimaster),到手快一年了还没有完全解锁线上的关卡,倒不是玩不下去,而是懒,刷 B 站多香,动啥脑子,哈哈哈。言归正传,本篇记录自己解锁线上关卡的步骤,持续更新 + +## 进度 + +0. 初 ------------------ 100% + - ✅ 梦入紫禁 + - ✅ 太和异象 + - ✅ 十八棵槐 +1. 壹 ------------------ 23% + - 万寿盛筵 + - 殿前观礼 + - 礼乐度量 + - 一等画师 + - 腰牌买卖 + - 慈宁画样 +2. 贰 ------------------ 0% + - 宫女禾心 + - 嘉祉初遇 + - 淑芳听戏 + - 戏里玄机 + - 上元之约 +3. 叁 ------------------ 0% + - 结伴寻宝 + - 皇十五子 + - 档房探秘 + - 一路狂奔 + - 逢凶化吉 + - 五行八卦 + - 夜探御园 +4. 肆 ------------------ 0% + - 琳琅宝藏 + - 花叶之谜 + - 宫中怪人 + - 图籍作者 + - 祸不单行 + - 五蕴皆空 +5. 伍 ------------------ 0% + - 将破未破 + - 孤注一掷 +6. 隐 ------------------ 0% + - 多年以后 +7. 众 ------------------ 0% + - 众筹专属 + +## 初 + +### 梦入紫禁 +### 太和异象 +### 十八棵槐 + +## 壹 + +### 万寿盛筵 +### 殿前观礼 +### 礼乐度量 +### 一等画师 +### 腰牌买卖 +### 慈宁画样 + +## 贰 + +### 宫女禾心 +### 嘉祉初遇 +### 淑芳听戏 +### 戏里玄机 +### 上元之约 + +## 叁 + +### 结伴寻宝 +### 皇十五子 +### 档房探秘 +### 一路狂奔 +### 逢凶化吉 +### 五行八卦 +### 夜探御园 + +## 肆 + +### 琳琅宝藏 +### 花叶之谜 +### 宫中怪人 +### 图籍作者 +### 祸不单行 +### 五蕴皆空 + +## 伍 + +### 将破未破 +### 孤注一掷 + +## 隐 + +### 多年以后 + +## 众 + +### 众筹专属 + +## 附录 + +1. 奥秘之家官网:http://www.itaotuo.com/ +2. 《唐人街探案 2》之《侦探笔记》:https://www.zhihu.com/question/267341464 +3. 《唐人街探案 3》之《侦探笔记》:https://zhongchou.modian.com/item/90315.html \ No newline at end of file diff --git a/source/_posts/play-ssd.md b/source/_posts/play-ssd.md new file mode 100644 index 000000000..e8c887977 --- /dev/null +++ b/source/_posts/play-ssd.md @@ -0,0 +1,23 @@ +--- +title: 搞定 m.2 接口 SSD +date: 2020-12-10 09:44:46 +categories: Play +tag: [SSD] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1611489458/blog/dell-ssd.jpg) + + + +公司原装配置电脑磁盘性能太差,实在是不能满足我的日常骚操作,然后就自己买了一个 m.2 接口的 SSD 硬盘,毕竟电脑之前已经有系统了,而且也已经安装好了开发环境,如果现在在新的 SSD 上直接安装新的系统,那么需要将之前的开发环境再折腾一遍,实在是伤不起。那么有没有别的方式。你别说哦,还真的有,方法是用一些工具对现有系统进行 clone 到新的 SSD 磁盘上。这都很好办,比如:[傲梅分区助手](https://www.disktool.cn),[DiskGenius](https://www.diskgenius.cn) 都有系统迁移功能,可参考文章下方的参考地址,内有视频教程 + +>注意:要设置好设置默认系统启动引导为新的磁盘 + +## 问题 + +一开始,我觉得这么简单的操作能有什么问题,迁移完系统,并设置好系统引导,然而我发现并不能按照预期使用 SSD 来启动,试了好几遍,调整了 BIOS 的启动选项,依旧不能解决。后来我将原系统的磁盘拆下来,只留 SSD 磁盘,开机就能按照预期启动了,正常后在把原系统磁盘再装回去,同时记得检查下系统引导,确保还依旧是使用 SSD 系统盘 + +## 参考 + +1. [SSD系统迁移工具:轻松迁移系统到SSD](https://www.disktool.cn/jiaocheng/migrate-system.html) +2. [使用分区助手快速将Windows系统迁移到新磁盘](https://www.disktool.cn/jiaocheng-new/2019/how-to-migrate-system.htmlv) \ No newline at end of file diff --git a/source/_posts/rap1.md b/source/_posts/rap1.md new file mode 100644 index 000000000..4828e3963 --- /dev/null +++ b/source/_posts/rap1.md @@ -0,0 +1,78 @@ +--- +title: Api 文档管理系统 RAP1环境搭建 +date: 2018-03-27 10:19:25 +categories: Api +tag: RAP +--- + +前后端分离的路上,一款强大的API管理工具,可以降低沟通成本,大大提高开发效率,节省的时间,让我们去做更有意义的事情。 + +API管理工具有很多,选择适合自身需求的就是最好 + +这里以[阿里妈妈](https://thx.github.io)出品的[RAP](https://thx.github.io/RAP)产品;目前RAP分为: ~~[RAP1](https://github.com/thx/RAP)~~,[RAP2](https://github.com/thx/rap2-delos) + + + +>虽然RAP1不再添加新功能,只做维护工作,介于RAP2目前还不是很成熟,本篇文章先讲RAP1的搭建过程(虽然官方[Wiki](https://github.com/thx/RAP/wiki)已经有很详细的部署教程,但在部署过程中还是遇到一些问题,因此就记录下来) + +如果你不需要搭建,可以直接访问RAP1提供的服务[http://rapapi.org](http://rapapi.org) + +## 项目构建 + +* 系统环境:Windows 10 x64 +* 应用工具:[Git](https://git-scm.com/downloads),[IDEA](https://www.jetbrains.com/idea/download),[JDK1.8+](https://www.java.com/zh_CN/download/manual.jsp),[Tomcat8+](https://tomcat.apache.org/download-80.cgi),[MySQL](https://www.mysql.com/cn/downloads),[Redis3+](https://redis.io/download) + +这里Git,IDEA,JDK1.8,Tomcat8,MySQL不再赘述安装步骤以及环境配置 + +### 安装基本工具 +#### Redis +由于Redis 官方并未支持Windows系统,因此借助MicrosoftArchive团队所提供的[Windows Redis安装包](https://github.com/MicrosoftArchive/redis/releases),这里下载最新的`Redis-x64-3.2.100.msi` + +* 以管理员身份运行安装包`Redis-x64-3.2.100.msi` + 1. 添加环境变量 + ![env](https://res.cloudinary.com/incoder/image/upload/v1525517089/blog/gitpages-redis-env-var.png) + 2. 默认`6379`端口 + ![port](https://res.cloudinary.com/incoder/image/upload/v1525517270/blog/gitpages-redis-port.png) + 3. 检查Redis服务,是否已经启动 + ![serve](https://res.cloudinary.com/incoder/image/upload/v1525517284/blog/gitpages-redis-serve.png) + + >其他默认即可,不要设置Memory Limit + +## 构建项目 +### 获取源代码 +```sh +git clone git@github.com:thx/RAP.git +git checkout release +``` + +>确保您正确的切换到release分支,否则会出现少包,因为master分支引用一些不对外公开的内部组件,不提供给外部用户使用 +### 导入到IDEA +IDEA==>Open==>RAP + +### 初始化数据库 +执行脚本文件:RAP\src\main\resources\database\\`initialize.sql` + +### 修改配置文件 +文件:RAP\src\main\resources\database\\`config.properties` +修改:数据库`用户名`及`密码` +![update](https://res.cloudinary.com/incoder/image/upload/v1525517302/blog/gitpages-rap1-update-database-config.png) +## 启动项目 +1. Edit config +![config](https://res.cloudinary.com/incoder/image/upload/v1525517326/blog/gitpages-rap1-tomcat-config.png) +2. Create Tomcat +![create](https://res.cloudinary.com/incoder/image/upload/v1525517350/blog/gitpages-rap1-tomcat-create.png) +3. Deploy war +![deploy](https://res.cloudinary.com/incoder/image/upload/v1525517364/blog/gitpages-rap1-tomcat-deploy.png) +4. Deploy success +![success](https://res.cloudinary.com/incoder/image/upload/v1525517384/blog/gitpages-rap1-deploy-success.png) + +注意成功部署后,请`注册`新账号登录 + +至此,RAP1的本机部署已经完成。 + +## 其他 +* [RAP1学习中心](http://thx.github.io/RAP/study.html) + 部分同学无法查看视频,请异步至[issues](https://github.com/thx/RAP/issues/935) +* [RAP1 Wiki](https://github.com/thx/RAP/wiki)文档 +* [Mockjs](http://mockjs.com) +* [RAP2环境搭建教程](https://www.incoder.org/2018/03/27/rap2) \ No newline at end of file diff --git a/source/_posts/rap2.md b/source/_posts/rap2.md new file mode 100644 index 000000000..257f4764a --- /dev/null +++ b/source/_posts/rap2.md @@ -0,0 +1,256 @@ +--- +title: Api 文档管理系统 RAP2环境搭建 +date: 2018-03-27 10:20:10 +categories: Api +tag: RAP +--- + +RAP2是采用前后端分离的形式,因此搭建完整的RAP2需要 **服务端:**[rap2-delos](https://github.com/thx/rap2-delos),**客户端:**[rap2-dolores](https://github.com/thx/rap2-dolores) 同时部署 + +部署RAP2需要亲具有Node+Linux+MySQL的运维知识,如果亲对此不是很了解,建议用[http://rap2.taobao.org](http://rap2.taobao.org) 线上版本就可以 + +由于 **客户端:**[rap2-dolores](https://github.com/thx/rap2-dolores) 是建立在 **服务端:**[rap2-delos](https://github.com/thx/rap2-delos) 基础上,因此先搭建服务端应用 + + + +个人贡献 [📖 issues 119](https://github.com/thx/rap2-delos/issues/119) + +{% note danger %} +* 截至 2018-08-01 [delos](https://github.com/thx/rap2-delos) 并没有发布 Tag版本,应该还处于功能开发前期阶段吧。本教程是在CentOS机器上实战部署 +* 然而安装部署并不是顺利,因此记录踩过的坑(别问我为啥不用Docker,因为我司分配的机器无法满足Docker的最低内核版本),安装环境介绍:Redis,delos,dolores均在一台服务器,MySQL使用已存在的服务 +* 本篇文章最后更新于 2018-08-01,因此后续的项目部署相关,**请参考官方部署教程** +{% endnote %} + +## 安装基本工具 +* [Git](https://git-scm.com/downloads) +* [Node 8.9.4+](https://nodejs.org/zh-cn/download) +* [Redis 4.0+](https://redis.io/download) +* [MySQL 5.7+](https://www.mysql.com/cn/downloads) + +以上基本工具请根据自身需要,下载对应系统安装包,请自行解决安装配置等问题,这里不做过多说明 + +> Redis 安装可参考[Linux 常用应用安装](https://incoder.org/2018/05/15/linux-build); +Redis 最好用**非安全**模式启动 + +## 服务端delos环境搭建 + +### 构建项目 + +> 构建项目前,请确认Node,Redis,MySQL服务均能正常使用 + +``` sh +git clone https://github.com/thx/rap2-delos.git +``` + +### 环境配置 + +#### 创建数据库 + +* Mac or Linux + ```sql + mysql -e 'CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP DEFAULT CHARSET utf8 COLLATE utf8_general_ci'; + ``` +* Windows 环境 + + 进入mysql命令后执行 + ```sql + CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP DEFAULT CHARSET utf8 COLLATE utf8_general_ci; + ``` +#### 配置文件 + +目录:rap2-delos/src/config +文件:`config.dev.ts`;其中dev,表示开发环境,其他同理 +修改:`config.dev.ts`文件中`db`对象中`username`,`password`参数与**本地**或者**开发环境**的数据库信息匹配 + +### 启动项目 + +#### 安装项目依赖包 + +项目根目录下执行 + +```sh +# 安装项目所需依赖 +npm install +# 全局安装PM2 +npm install -g pm2 +``` + +#### 安装TypeScript编译包 + +```sh +npm install typescript -g +``` + +> 如果下载缓慢,请使用[淘宝npm镜像](https://npm.taobao.org) + +#### 初始化数据库 + +项目根目录下执行(该过程比较慢,耐心等待初始化完成) + +```npm +npm run create-db +``` + +#### 编译启动项目 + +执行mocha测试用例和js代码规范检查 +```sh +npm run check +``` + +* 开发模式 +启动开发模式的服务器 监视并在发生代码变更时自动重启(第一次运行比较慢,请耐心等待) + ```sh + npm run dev + ``` +* 生产模式 + 启动生产模式服务器 + ```sh + npm start + ``` + +看到浏览器中如下提示,表示**服务端delos**已经部署成功 +>RAP2后端服务已启动,请从前端服务(rap2-dolores)访问。 RAP2 back-end server is started, please visit via front-end service (rap2-dolores). + +或者在程序控制台出现如下Log,表示**服务端delos**已经部署成功 +![delos](https://res.cloudinary.com/incoder/image/upload/v1525517437/blog/gitpages-rap2-delos-success.png) + +### 常见问题 + +#### 部署问题 + +1. Windows下执行 `npm run build`,提示`'rm' 不是内部或外部命令,也不是可运行的程序或批处理文件` + + 原因:`rm` 是Linux下命令, + 解决方法:Windows系统可使用 `git bash` 打开该项目,执行该命令 + +2. 执行 `npm run create-db` 命令,提示 + ` + Unable to connect to the database:{ SequelizeAccessDeniedError: Access denied for user 'root'@'localhost' (using password:NO)} + ` + + 原因:未修改 `rap2-delos/src/config` 目录下数据库配置文件,或者是与文件中的数据库信息与之连接的数据库信息不匹配 + 解决方法:修改 `config.dev.ts` 文件数据库配置信息 + >如果修改正确无误后,执行 `npm run create-db` 依旧出错,那么查看该项目中是否已经存在 `dist` 目录,如果有,请按照如上修改对应的数据库配置信息 +3. 执行 `npm run dev` 命令,提示 `Error: listen EADDRINUSE :::8080` + 原因:8080端口被占用 + 解决方法:杀掉占用8080端口的应用 +4. 执行 `npm install` 命令,提示 `hiredis` 编译无法通过 + 原因:无权限操作`rap2-delos/node_modules/hiredis`路径 + 解决方法:`sudo npm install` + > 如果提示`sudo: npm: command not found`,请参考 [stackoverflow-npm](https://stackoverflow.com/questions/31472755/sudo-npm-command-not-found),[stackoverflow-node](https://stackoverflow.com/questions/4976658/on-ec2-sudo-node-command-not-found-but-node-without-sudo-is-ok) +5. 执行 `npm run dev` 可以正常启动,`npm start` 命令无法正常启动服务 + 原因:请使用 `pm2 logs` 查看日志具体定位 + 示例:由于Redis的安全模式,不能正常使用 + ```bash + ReplyError: Ready check failed: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: + + 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. + 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. + 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. + 4) Setup a bind address or an authentication password. + NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside. + ``` + 解决方法: 使用`--protected-mode no`方式启动 + +## 客户端dolores环境搭建 + +### 构建项目 + +#### 获取源代码 + +``` sh +git clone https://github.com/thx/rap2-dolores.git +``` + +### 环境配置 + +#### 配置文件 + +目录:rap2-dolores/src/config +文件:`config.dev.ts`;其中dev,表示开发环境,其他同理 +修改:`config.dev.ts` 配置文件 `serve` 的地址,更改为 **服务端**(`rap2-delos`)部署成功后的地址,默认:`'http://localhost:8080'` + +### 启动项目 + +#### 安装项目依赖包 + +项目根目录下执行 + +```sh +npm install +``` + +> 如果下载缓慢,请使用[淘宝npm镜像](https://npm.taobao.org) + +#### 编译启动项目 + +* 开发模式 +自动监视改变后重新编译 + ```sh + npm run dev + ``` + 备注:测试用例 + ```sh + npm run test + ``` +* 生产模式 +编译React生产包 + ```sh + npm run build + ``` + 用serve命令或nginx服务器路由到编译产出的build文件夹作为静态服务器即可 + ```sh + serve -s ./build -p 80 + ``` + +看到浏览器中出现登录页面,表示部署成功 +![dolores](https://res.cloudinary.com/incoder/image/upload/v1525517454/blog/gitpages-rap2-dolores-success.png) + +### 常见问题 + +#### 部署问题 + +1. 执行`npm run dev`,提示 + ```sh + return process.dlopen(module,path._makeLong(filename)) + ... + ...node_modules\node-sass\vendor\win32-x64-57\binding.node is not a valid Win32 application... + ``` + + 原因:项目依赖包`node-sass`没有安装完全 + 解决方法:`npm install node-sass` + +2. 项目运行起来,但一直停留在加载动画那里 + + 浏览器控制台输出: + ``` + GET http://127.0.0.1:8080/account/info ==>> + Failed to load http://127.0.0.1:8080/account/info + ``` + 原因:未修改`rap2-delos/src/config`目录下服务端连接地址,或者修改结果与[rap2-dolores](https://github.com/thx/rap2-dolores)实际提供服务地址不匹配 + 解决方法:修改`config.dev.ts`文件serve配置信息 + >如果Windows系统修改正确无误后,依旧出错,查看hosts(路径:C:\Windows\System32\drivers\etc)中127.0.0.1的IP前是否有`#`,如果有请取消注释 + +## 其他 + +### MySQL 运行问题 + +* 错误一 +![mysql](https://res.cloudinary.com/incoder/image/upload/v1525517475/blog/gitpages-rap2-mysql.png) +原因:MySQL 集成命令没有加入系统的环境变量 +解决方法:将安装的MySQL Service路径加入系统变量 +![path](https://res.cloudinary.com/incoder/image/upload/v1525517495/blog/gitpages-rap2-mysql-path.png) +* 错误二 +![create](https://res.cloudinary.com/incoder/image/upload/v1525517523/blog/gitpages-rap2-mysql-create.png) +原因:没有数据库链接权限 +解决方法:先登录用root数据库,密码具体看自己数据库当时设置的密码 + +### 如何获取更新 + +目前请选择 `master` 分支源码,后续其他分支请看相应分支说明文档。在开发环境中git pull来获取最新的源码更新,每一期更新都会有对应的update.md请关注并按照上面的指示进行升级工作。 + +## 附录 +* [Redis如何后台启动](https://blog.csdn.net/ksdb0468473/article/details/52126009) +* [Redis配置文件介绍](http://www.cnblogs.com/ysocean/p/9074787.html) +* [PM2实用入门指南](https://www.cnblogs.com/chyingp/p/pm2-documentation.html) \ No newline at end of file diff --git a/source/_posts/realm.md b/source/_posts/realm.md new file mode 100644 index 000000000..4c69889a4 --- /dev/null +++ b/source/_posts/realm.md @@ -0,0 +1,214 @@ +--- +title: Realm 数据库快速上手 +date: 2018-04-24 01:11:10 +categories: [DataBase, Realm] +tag: Realm +--- + +![realm-db](https://res.cloudinary.com/incoder/image/upload/v1525517554/blog/gitpages-realm-mobile-db.png) + + + +Android 供了多种选项来保存永久性应用数据。 + +* [Shared preferences](https://developer.android.google.cn/guide/topics/data/data-storage.html?hl=zh-cn#pref) +* [Internal file storage](https://developer.android.google.cn/guide/topics/data/data-storage.html?hl=zh-cn#filesInternal) +* [External file storage](https://developer.android.google.cn/guide/topics/data/data-storage.html?hl=zh-cn#filesExternal) +* [Databases](https://developer.android.google.cn/guide/topics/data/data-storage.html?hl=zh-cn#db) +* [Network](https://developer.android.google.cn/guide/topics/data/data-storage.html?hl=zh-cn#netw) + +其中数据库存储是一种必备技能,而衍生的mobile db也是层出不穷,本节主要介绍全平台(除Android,iOS,macOS外还支持web,桌面应用)[Realm](https://realm.io)数据库在Android上的使用 + +## 快速上手 + +* [Android Studio 1.5.1+](https://developer.android.google.cn/studio/index.html?hl=zh-cn) +* JDK1.7+ +* Android API 9+ +* Realm 默认情况下使用内部存储(internal storage),一般来说,这个文件位于`/data/data//files/`,文件名:`default.realm` + +### 集成 + +* 在项目的 build.gradle 文件中添加如下 class path 依赖 + + ```groovy + buildscript { + repositories { + jcenter() + } + dependencies { + classpath "io.realm:realm-gradle-plugin:5.0.0" + } + } + ``` + +* 在 app 的 build.gradle 文件中应用 realm-android 插件 + + ```groovy + apply plugin: 'realm-android' + ``` + +### 初始化 + +* 默认初始化 + + ```java + public class MyApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + // 默认Realm的配置文件 + Realm.init(this); + } + } + ``` + +* 自定义初始化 + + ```java + public class MyApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + // 自定义配置Realm + initRealm(); + } + + private void initRealm() { + RealmConfiguration config = new RealmConfiguration.Builder() + .name("myrealm.realm") // 命名文件名:myrealm.realm + .inMemory() // 一个非持久化的、存在于内存中的 Realm 实例 + .encryptionKey(getKey()) // 数据库加密key + .schemaVersion(2) // 数据库结构版本号 + .modules(new MySchemaModule()) // 数据库结构对象 + .migration(new MyMigration()) // 数据库迁移 + .build(); + Realm.setDefaultConfiguration(config); + } + } + ``` + + >1. Realm 实例是线程单例化的,也就是说多次在同一线程调用静态构建器会返回同一 Realm 实例 + >2. 使用同样的名称同时创建“内存中的”Realm 和常规的(持久化)Realm 是不允许的 + +### 字段类型 + +Realm 支持以下字段类型:`boolean`、`byte`、`short`、`int`、`long`、`float`、`double`、`String`、`Date`和`byte []`。整数类型 `short`、`int` 和 `long` 都被映射到 Realm 内的相同类型(实际上为 `long` )。 + +* @Required修饰类型和空值(null) + >Realm强制禁止空值(null)被存储 + 只有`Boolean`,`Byte`,`Short`,`Integer`,`Long`,`Float`,`Double`,`String`,`byte[]`,`Date`可被修饰 +* @Ignore标识一个字段不应该被保存到 Realm +* @Index为字段增加搜索索引 + > 仅支持索引的属性类型包括:`String`,`byte`,`short`,`int`,`long`,`boolean`和`Date` +* @PrimaryKey + > 必须为字符串(`String`)或整数(`short`,`int`,`long`)以及它们的包装类型(`Short`,`Int`,`Long`) + +### 声明Realm数据模型 + +#### RealmObject + +可以把RealmObject 当作POJO使用 + +```java +public class User extends RealmObject { + +} +``` + +#### RealmModel + +```java +@RealmClass +public class User implements RealmModel { + +} +``` + +### 关系 + +#### 多对一 + +```java +public class Contact extends RealmObject { + private Email email; + // Other fields… +} + +public class Email extends RealmObject { + private String address; + private boolean active; + // ... setters and getters left out +} +``` + +#### 多对多 + +```java +public class Contact extends RealmObject { + public String name; + public RealmList emails; +} + +public class Email extends RealmObject { + public String address; + public boolean active; +} +``` + +### CRUD + +* 所有的写操作(添加、修改和删除对象),必须包含在写入事务(transaction)中 +* 在提交期间,所有更改都将被写入磁盘,并且,只有当所有更改可以被持久化时,提交才会成功。通过取消一个写入事务,所有更改将被丢弃。 +* 益于 Realm 的 MVCC 架构,当正在进行一个写入事务时读取操作并不会被阻塞!这意味着,除非你需要从多个线程进行并发写入操作,否则,你可以尽量使用更大的写入事务来做更多的事情而不是使用多个更小的写入事务。 + +#### 增 + +* 事务执行 + + ```java + Realm realm = Realm.getDefaultInstance(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + User user = realm.createObject(User.class); + user.setName("John"); + user.setEmail("john@corporation.com"); + } + }); + ``` + +* 异步事务 + + ```java + Realm realm = Realm.getDefaultInstance(); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm bgRealm) { + User user = bgRealm.createObject(User.class); + user.setName("John"); + user.setEmail("john@corporation.com"); + } + }, new Realm.Transaction.OnSuccess() { + @Override + public void onSuccess() { + // Transaction was a success. + } + }, new Realm.Transaction.OnError() { + @Override + public void onError(Throwable error) { + // Transaction failed and was automatically canceled. + } + }); + ``` + + >OnSuccess 和 OnError 并不是必须重载的,重载了的回调函数会在事务成功或者失败时在被调用发生的线程执行。 + +#### 删 + +#### 改 + +#### 查 + +## Realm进阶 + +## Realm云 diff --git a/source/_posts/rxjava.md b/source/_posts/rxjava.md new file mode 100644 index 000000000..acd08404c --- /dev/null +++ b/source/_posts/rxjava.md @@ -0,0 +1,374 @@ +--- +title: RxJava 入门 +date: 2018-10-02 10:02:00 +categories: RxJava +tag: [RxJava] +--- + +RxJava – Reactive Extensions for the JVM – a library for composing **asynchronous** and **event-based** programs using **observable** sequences for the Java VM.(一个在 Java VM 上使用{% label info@可观测 %}的序列( **观察者模式** )来组成{% label info@异步 %}的、{% label info@基于事件 %}的程序的库). + +在实际开发过程中,RxJava已是一个不可或缺的组件,因此对于RxJava的学习和思考,记录分享是很重要的一个环节 + +本系列文章主要: +1. [RxJava 入门](https://incoder.org/2018/10/02/rxjava/) +2. RxJava 实际应用 +3. RxJava 源码剖析 + + + +目前来说,RxJava有两个版本,RxJava1 与 RxJava2 两个版本之间虽然存在很多不同,但它们的本质是相同,由于对于RxJava1 **已废弃**,因此建议没有学习或者是使用过,可直接上手学习RxJava2(在学习过程中部分地方还是会有RxJava1相关的说明,但这不是重点) + +文章使用RxJava版本如下: +* `implementation 'io.reactivex:rxjava:1.3.0'` +* `implementation 'io.reactivex.rxjava2:rxjava:2.2.1'` +* 项目示例:[rc-cluster-network](https://github.com/RootCluster/rc-cluster-network) + +>由于一个项目中RxJava1与RxJava2并不能共存,因此实际参考项目中仅RxJava2示例 + +## RxJava 基础 + +### RxJava1 VS RxJava2 + +![rxjava1 vs rxjava2](https://res.cloudinary.com/incoder/image/upload/v1538815280/blog/RxJava1_vs_RxJava2.png) +>以上是列举出不同版本间主要的变换,其它更细节部分,请查看官方[Wiki](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0) + +### 关键词 + +#### RxJava1 +* Observable (可观察者,即被观察者) +* Observer (观察者) +* subscribe (订阅) +* 事件 + +>Observable和Observer通过subscribe()方法实现订阅关系,从而Observable可以在需要的时候发出事件来通知Observer + +#### RxJava2 +* Observable (可观察者,即被观察者) +* Observer (观察者) +* ObservableEmitter (发射器) +* 事件 + +>RxJava2中`Subscrber`被`ObservableEmitter`取代,`Observer`中多了一个回调方法 `onSubscribe()`,传递参数为`Disposable` + +* ObservableEmitter:Emitter是发射器的意思,这个就是用来发出事件,它可以发出三种类型的事件,通过调用`emitter`的`onNext(T value)`,`onComplete()`和`onError(Throwable e)`就可以分别发出`next`事件,`complete`事件和`error`事件 +* Disposable:字面意思是一次性用品,用完即可丢弃。在RxJava中可以理解成两根管道间的阀门,当调用它的的`dispose()`方法时,它就将两根管道切断,从而导致下游收不到事件,即相当于`Subsciption` + +### 基本实现 + +#### Create Observable +![operators](https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/legend.png) + +##### RxJava1 + +```java +Observable observable = Observable.unsafeCreate(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + subscriber.onNext("Hello"); + subscriber.onNext("World"); + subscriber.onNext("RxJava1"); + subscriber.onCompleted(); + } +}); +``` +>1.2.7版本后,Observable的`create()`方法已被废弃,如果没有特殊需求,可以使用`unsafeCreate()`代替,构造Obaservable实例 + +##### RxJava2 + +```java +Observable observable = Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter emitter) throws Exception { + emitter.onNext("Hello"); + emitter.onNext("World"); + emitter.onNext("RxJava2"); + emitter.onComplete(); + } +}); +``` +`unsafeCreate()/create()`方法是RxJava最基本创建时间序列的方法。基于这个方法,RxJava还提供了一些方法来快捷创建事件队列 + +* just(T...):将传入的参数依次发送出来 + ```java + Observable observable = Observable.just("Hello", "World", "RxJava"); + // 将会依次调用: + // onNext("Hello"); + // onNext("World"); + // onNext("RxJava"); + // onCompleted(); + ``` +* from(T[])/from(Iierabble):将传入的数组或Iterable拆分成具体对象后,依次发送出来 + ```java + String[] words = {"Hello", "World", "RxJava"}; + Observable observable = Observable.from(words); + // 将会依次调用: + // onNext("Hello"); + // onNext("World"); + // onNext("RxJava"); + // onCompleted(); + ``` + +##### Flowable +Flowable是RxJava2中新增的类,专门应对背压(Backpressure)问题,但这个概念并不是RxJava2中引入的概念。 + +出现Flowable的原因:即生产者(被观察者发送事件)的速度与消费者(观察者接收所有事件)的速度不匹配,从而导致观察者无法及时响应/处理所有发送过来的事件问题,最终导致缓冲区溢出,事件丢失 & OOM等问题。 + +一般情况,被观察者发送事件速度 > 观察者接收事件速度。比如:点击过快造成等 + +```java +Flowable.create(new FlowableOnSubscribe() { + + @Override + public void subscribe(FlowableEmitter emitter) throws Exception { + emitter.onNext("Hello"); + emitter.onNext("World"); + emitter.onNext("RxJava2"); + emitter.onComplete(); + } +}, BackpressureStrategy.ERROR) + .subscribeOn(Schedulers.computation()) + .observeOn(Schedulers.newThread()) + .subscribe(new Consumer() { + @Override + public void accept(String s) throws Exception { + // 相当于onNext + Thread.sleep(1000); + System.out.println("accept"); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + // 相当于onError + System.out.println("accept" + throwable.toString()); + } + }); +``` +Flowable并不是订阅就开始发送数据,而是需等到执行`Subscription.request()`才开始发送数据 + +#### Create Observer + +##### RxJava1 +```java +Observer observer = new Observer() { + @Override + public void onCompleted() { + System.out.println("Completed!"); + } + + @Override + public void onError(Throwable e) { + System.out.println("Error" + e); + } + + @Override + public void onNext(String s) { + System.out.println("Next" + s); + } +}; +``` +除了`Observer`接口之外,RxJava内置了一个实现`Observer`的抽象类`Subscriber`,`Subscriber`对`Observer`接口进行了一些扩展,但它们的基本使用方式是完全一样 +```java +Subscriber subscriber = new Subscriber() { + @Override + public void onCompleted() { + System.out.println("Completed!"); + } + + @Override + public void onError(Throwable e) { + System.out.println("Error" + e); + } + + @Override + public void onNext(String s) { + System.out.println("Next" + s); + } +}; +``` +实际,在RxJava的subscribe过程中,`Observer`也总是会先被转成一个`Subscriber`再使用。对于使用者来说`Observer`与`Subscriber`的主要区别是: +1. onStart():这是`Subscriber`增加的方法。它会再subscribe刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如:数据的重置等操作。这是一个可选方法,默认情况下它的实现为空。 +>注意: +>对于准备工作有线程要求,`onStart()`就不适用,因为它总是再subscribe所发生的线程被调用,而不能指定线程。要指定线程来准备工作,可以使用`doOnSubscribe()`方法 +2. unsubscribe():`Subscriber`所实现的另一个接口`Subscription`的方法,用于取消订阅。在这个方法被调用后,`Subscriber`将不再接收事件。 +>注意: +>* 一般需要在调用`unsubscribe()`方法前,需要使用`isUnsubscribed()`先判断状态。 +>* 不再使用的时候尽快在合适的地方调用`unsubscribe()`来解除引用关系,以避免内存泄漏 + +##### RxJava2 +```java +Observer observer = new Observer() { + @Override + public void onSubscribe(Disposable d) { + System.out.println("Subscribe: " + d); + } + + @Override + public void onNext(String s) { + System.out.println("Next: " + s); + } + + @Override + public void onError(Throwable e) { + System.out.println("Error: " + e); + } + + @Override + public void onComplete() { + System.out.println("Complete !"); + } +}; +``` + +#### Subscribe +创建好`Observable`和`Observer`之后,再用`subscribe()`方法将它们联结起来 +```java +observable.subscribe(observer); +// 或者(仅支持RxJava1) +observable.subscribe(subscriber); +``` + +#### chain calls +以上三步是使用RxJava进行异步操作的基本过程,创建`被观察者`,创建`观察者`,`被观察者`订阅`观察者`,我们可以通过链式调用形式完成操作 + +```java +Observable.unsafeCreate(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + subscriber.onNext("Hello"); + subscriber.onNext("World"); + subscriber.onNext("RxJava1"); + subscriber.onCompleted(); + } +}).subscribe(new Observer() { + @Override + public void onCompleted() { + System.out.println("Completed!"); + } + + @Override + public void onError(Throwable e) { + System.out.println("Error" + e); + } + + @Override + public void onNext(String s) { + System.out.println("Next" + s); + } +}); +``` +#### 简化订阅 +除了`subscribe(Observer)`和`subscribe(Subscriber)(仅支持RxJava1)`,`subscribe()`还支持不完整的简化订阅回调 + +```java +// RxJava1 +Action1 onNextAction = new Action1() { + @Override + public void call(String s) { + System.out.println("onNext" + s); + } +}; + +Action1 onErrorAction = new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError" + throwable); + } +}; + +Action0 onCompletedAction = new Action0() { + @Override + public void call() { + System.out.println("completed"); + } +}; + +// RxJava2 +Consumer onNextAction = new Consumer() { + @Override + public void accept(String s) throws Exception { + System.out.println("onNext" + s); + } +}; + +Consumer onErrorAction = new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + System.out.println("onError" + throwable); + } +}; + +Action onCompleteAction = new Action() { + @Override + public void run() throws Exception { + System.out.println("complete"); + } +}; + +// 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext() +observable.subscribe(onNextAction); +// 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和 onError() +observable.subscribe(onNextAction, onErrorAction); +// 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来定义 onNext()、 onError() 和 onCompleted() +observable.subscribe(onNextAction, onErrorAction, onCompletedAction/onCompleteAction); + +``` + +## RxJava 线程 +在RxJava的默认规则中,事件的发出和消费都是在同一个线程(在哪个线程条用`subscriber()`,就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件),也就是说,以上RxJava基本操作,实现出来的只是一个`同步`的观察者模式。而观察者模式本身的目的是“后台处理,前台回调”的`异步`机制,因此在RxJava中通过`Scheduler`来对线程进行管理 + +### Scheduler API +Scheduler相当于线程控制器,RxJava通过它指定代码应该运行在什么样的线程,其中RxJava中内置了几个Scheduler +* Schedulers.computation():计算所使用的`Scheduler`。这个计算值的是CPU密集型计算,即不会被I/O操作等限制性能的操作。不要把I/O操作放在`computation()`中,否则I/O操作的等待时间会浪费CPU。 +* Schedulers.form(Executor): +* Schedulers.immediate():直接在当前线程运行,相当于不指定线程,这也是默认的Scheduler. +* Schedulers.io():I/O操作(读写文件,读写数据库,网络信息交换等)所使用的Scheduler。行为模式和newThread()差不多,区别在于io()的内部实现是一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下io()比newThread()更高效 +* Schedulers.newThread():总是启用新线程,并在新线程执行操作 +* Schedulers.single()『仅RxJava2中存在』: +* Schedulers.test()『仅RxJava1中存在』:顾名思义,这是一个测试 +* Schedulers.trampoline(): +* AndoroidSchedulers.mainThread():指定操作在Android的主线程 + +有了Scheduler,我们可以使用`subscribeOn()`和`observeOn()`方法来对线程进行控制 +* subscribeOn():指定subscribe()所发生的线程,即Observable.OnSubscribe被激活时所处的线程,或者叫做事件的产生的线程 +* observeOn():指定Subscriber所运行的线程。或者叫做事件的消费线程 + + ```java + // RxJava2 + Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter emitter) throws Exception { + emitter.onNext("Hello"); + emitter.onNext("World"); + emitter.onNext("RxJava2"); + emitter.onComplete(); + } + }) + // 指定 subscribe() 发生在 IO 线程 + .subscribeOn(Schedulers.io()) + // 指定 Subscriber 的回调发生在主线程 + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Consumer() { + @Override + public void accept(String s) throws Exception { + System.out.println(s); + } + }); + ``` + +## 操作符 +说RxJava好用,还有一个原因是RxJava提供了大量的操作符,这些操作符保证了在面都复杂的逻辑下,依旧可以是逻辑清晰的链式调用 + +![RxJava_action](https://res.cloudinary.com/incoder/image/upload/v1538815494/blog/RxJava_action.png) + +## 总结 +本篇文章作为RxJava系列的学习的入门,不会讲解相关操作的原理等 +学习目的 +* 了解RxJava1与RxJava2之间的不同点, +* 了解RxJava的线程管理, +* 掌握完成RxJava的基本操作, +* 清楚RxJava操作符,以及分别适用于什么样的场景 + +## 附录 +文章中部分原话引用了参考学习文章的原话,在这里向那些无私分享的大佬致敬 +* [给 Android 开发者的 RxJava 详解](https://gank.io/post/560e15be2dca930e00da1083) +* [RxJava系列教程](https://www.jianshu.com/nb/14302692) \ No newline at end of file diff --git a/source/_posts/scenically.md b/source/_posts/scenically.md new file mode 100644 index 000000000..ef365c142 --- /dev/null +++ b/source/_posts/scenically.md @@ -0,0 +1,63 @@ +--- +title: 琅嬛福地 +date: 2021-03-06 21:50:11 +categories: Resources +tag: DevTool +--- + +在金庸武侠《天龙八部》中,“琅嬛福地”存放了无崖子和李秋水搜罗天下各门各派的武功,江湖人士练成这里的一门武功绝学,就能在江湖中有自己的一席之地。而这里存放了我计算机相关学习、实践应用,以及经常使用的一些网站资源 + + + +## 计算机网络 + +{% linkgrid %} +方方方已经存在了 | https://www.bilibili.com/video/BV1yE411G7Ma | 计算机网络(谢希仁第七版). | https://i0.hdslb.com/bfs/face/abb12931aed341d6dcc67dd13162fddb35240622.jpg@96w_96h_1c.webp +韩立刚 | https://www.bilibili.com/video/BV1gV411h7r7 | 韩立刚计算机网络 谢希仁 第7版. | https://i1.hdslb.com/bfs/face/1814653848d0a645c053efa7a7b40b9c53929d38.jpg@96w_96h_1c.webp +王道论坛 | https://www.bilibili.com/video/BV19E411D78Q | 2019 王道考研 计算机网络. | https://i2.hdslb.com/bfs/face/507c26c8bca9a4b96ff7fb820da36c05960ea7ca.jpg@96w_96h_1c.webp +湖科大教书匠 | https://www.bilibili.com/video/BV1c4411d7jb | 计算机网络微课堂(陆续更新中......). | https://i0.hdslb.com/bfs/face/member/noface.jpg@96w_96h_1c.webp +中科大-郑老师 | https://www.bilibili.com/video/BV1JV411t7ow | 中科大郑烇老师全套《计算机网络(自顶向下方法 第7版,James F.Kurose,Keith W.Ross)》课程. | https://i2.hdslb.com/bfs/face/872982590cd7c2de9f5a3f595059a71fb9c95004.jpg@96w_96h_1c.webp +{% endlinkgrid %} + +## 数据结构与算法 + +{% linkgrid %} +临风笑笑生 | https://www.bilibili.com/video/BV11s41167h6 | 【郝斌】-数据结构入门. | https://i2.hdslb.com/bfs/face/48aa2e4e420c660b88fe3deef44975c296fd91bd.gif +星球杯25 | https://www.bilibili.com/video/BV1JW411i731 | 数据结构-浙江大学. | https://i2.hdslb.com/bfs/face/c38f4b346eed167f55183f1dc398376326c8ecc4.jpg@96w_96h_1c.webp +87师兄 | https://www.bilibili.com/video/BV1nJ411V7bd | 数据结构与算法基础(青岛大学-王卓). | https://i0.hdslb.com/bfs/face/4335c48c1a30a3d4e862c21eeb5f71b1218567ec.jpg@96w_96h_1c.webp +尚硅谷 | https://www.bilibili.com/video/BV1E4411H73v | 尚硅谷Java数据结构与java算法(Java数据结构与算法). | https://i2.hdslb.com/bfs/face/7ea5132de4ecdc8b594a98c11d9f224f0e741c0b.jpg@96w_96h_1c.webp +大雪菜 | https://space.bilibili.com/7836741 | LeetCode 的大神,刷题讲解. | https://i2.hdslb.com/bfs/face/55ea7d0f3b2038cec6b99d44068b3579f27065e5.jpg@128w_128h_1o.webp +{% endlinkgrid %} + +## 操作系统 + +{% linkgrid %} +绿导师原谅你了 | https://www.bilibili.com/video/BV1N741177F5 | 2020 南京大学 “操作系统:设计与实现” (蒋炎岩). | https://i1.hdslb.com/bfs/face/bc288c3544209fb2bdadaf45015721555175df17.jpg@96w_96h_1c.webp +星球杯25 | https://www.bilibili.com/video/BV1js411b7vg | 操作系统_清华大学(向勇、陈渝). | https://i1.hdslb.com/bfs/face/f81c108b4e7ced4b8e16c9a0d4ee3370e17e12bf.jpg@96w_96h_1c.webp +FCCJK | https://www.bilibili.com/video/BV1d4411v7u7 | 操作系统(哈工大李治军老师)32讲(全)超清. | https://i0.hdslb.com/bfs/face/51d41d81499b7913458145045e13107bf152b694.jpg@96w_96h_1c.webp +{% endlinkgrid %} + +## 计算机组成原理 + +{% linkgrid %} +绿导师原谅你了 | https://www.bilibili.com/video/BV1t4411e7LH | 计算机组成原理(哈工大刘宏伟). | https://i0.hdslb.com/bfs/face/51d41d81499b7913458145045e13107bf152b694.jpg@96w_96h_1c.webp +东南偏南2018 | https://www.bilibili.com/video/BV1c4411w7nd | 计算机组成原理 清华大学刘卫东 全58讲 国家精品课程 1080P 更完. | https://i0.hdslb.com/bfs/face/member/noface.jpg@96w_96h_1c.webp +{% endlinkgrid %} + +## 编译原理 + +{% linkgrid %} +执念缘不浅 | https://www.bilibili.com/video/BV1t4411e7LH | 编译原理(哈工大). | https://i0.hdslb.com/bfs/face/8ae1f165fef1fff75d8fced295a5f87b0f9d2e92.jpg@96w_96h_1c.webp +{% endlinkgrid %} + +## 资源 + +{% linkgrid %} +阿里云开发者藏经阁 | https://developer.aliyun.com/ebook | 各种实战经验,顶级技术电子书. | https://tse3-mm.cn.bing.net/th/id/OIP.RomlWHLG15NaBmLbJAHtvwAAAA?w=171&h=180&c=7&o=5&dpr=2&pid=1.7 +阿里云开发者实验室 | https://developer.aliyun.com/adc/labs | 免费云资源,真实云环境,丰富实践场景. | https://tse3-mm.cn.bing.net/th/id/OIP.RomlWHLG15NaBmLbJAHtvwAAAA?w=171&h=180&c=7&o=5&dpr=2&pid=1.7 +阿里云开发者学习中心 | https://developer.aliyun.com/learning | 各种学习路线图,热门技术训练营. | https://tse3-mm.cn.bing.net/th/id/OIP.RomlWHLG15NaBmLbJAHtvwAAAA?w=171&h=180&c=7&o=5&dpr=2&pid=1.7 +{% endlinkgrid %} + +## 参考 + +1. [聊一聊我在B站上自学编程的经历吧](https://mp.weixin.qq.com/s/2LKP53VVAhgl-R8H5YNaTQ) \ No newline at end of file diff --git a/source/_posts/security-rsa.md b/source/_posts/security-rsa.md new file mode 100644 index 000000000..c82126752 --- /dev/null +++ b/source/_posts/security-rsa.md @@ -0,0 +1,84 @@ +--- +title: 非对称加密——RSA +date: 2018-07-03 20:28:51 +categories: Security +tag: RSA +--- + +这是常用加密技术的系列文章,主要包含`非对称`,`对称`,`JWT`三类常用技术的应用 + +## RSA + +[RSA](https://zh.wikipedia.org/wiki/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95):RSA加密算法是一种 **非对称** 加密算法。在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。RSA就是他们三人姓氏开头字母拼在一起组成的。 + + + +### RSA加密解密 + +公钥 **加密** 私钥 **解密**,持有公钥(多人持有,**客户端**)可以对数据加密,但是只有持有私钥(一人持有,**服务端**)才可以解密并查看数据 + +### RSA加签验签 + +私钥 **加签** 公钥 **验签**,持有私钥(一人持有,**服务端**)可以加签,持有公钥(多人持有,**客户端**)可以验签 + +### RSA过程示意图 + +![security-rsa](https://res.cloudinary.com/incoder/image/upload/v1530793864/blog/security-rsa.png) + +如上图,具体表述两个场景过程 + +#### 结果不需加密 + +场景:返回的数据不需要加密(例如:绑定银行卡的时候) +* 客户端`Client A`发送使用服务端`Serve publicKey` **加密** 的密文`cipher A(包含用户的银行卡号,手机号等重要信息)`到服务器 +* 服务器`Serve` 通过 `Serve privateKey`**解密** +* 服务端业务处理完成,直接返回数据(一些普通信息,比如状态码code,提示信息msg,提示操作是成功还是失败)给客户端`Client A` + +#### 结果需加密 + +场景:返回的数据需要加密(例如:用户登录) +* 客户端`Client B`发送使用服务端`Serve publicKey` **加密** 的密文`cipher B(包含用户名和密码等重要信息)`以及客户端`Client B`的`Client B publicKey`到服务器 +* 服务器`Serve` 通过 `Serve privateKey`**解密** +* 服务端业务处理完成,直接返回数据(一般为token,token使用客户端`Client B`的`Client B publicKey`加密)给客户端`Client B` +* 客户端`Client B`使用`Client B privateKey`进行 **解密** 获取相应的用户信息等 + +## 密钥对 + +在使用RSA加密解密之前,首先要生成密钥对。所谓的密钥对,指的是公钥和私钥。RSA算法的密钥可以通过两个途径生成,一是借助`openssl`命令终端,二是使用`JDK`生成。 +本篇采用`JDK`方式生成密钥对,`openssl`方式可自行尝试 + +### JDK + +#### Serve端密钥对 + +#### Client端密钥对 + +##### Android密钥对 + +##### Web密钥对 + +##### iOS密钥对 + +### OpenSSL + +略... + +## RSA加密 + +## RSA解密 + +## RSA缺点 + +虽然RSA是一种较高级别加密机制,但也存在一些缺点 +1. 产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密。 +2. 安全性,RSA的安全性依赖于大数的因子分解,但并没有从理论上证明破译RSA的难度与大数分解难度等价,而且密码学界多数人士倾向于因子分解不是NP问题。 +3. 速度太慢,由于`RSA`的分组长度太大,为保证安全性,n 至少也要 `600 bit` 以上,使运算代价很高,尤其是速度较慢,较对称密码算法慢几个数量级;且随着大数分解技术的发展,这个长度还在增加,不利于数据格式的标准化。 + +## 附录 + +参考学习文章 +* [一张图了解RSA加解密与加验签](https://blog.csdn.net/zhshulin/article/details/71573542) +* [RSA加密解密及RSA加签验签](https://www.cnblogs.com/loveyou/p/7299524.html) +* [RSA加解密和加签验签](https://www.jianshu.com/p/ff9bd897e96a) +* [RSA加密解密样例](https://www.jianshu.com/p/283fff43a948) +* [RSA加密解密实现](https://blog.csdn.net/hustpzb/article/details/72734578) \ No newline at end of file diff --git a/source/_posts/soa.md b/source/_posts/soa.md new file mode 100644 index 000000000..4bd27b75d --- /dev/null +++ b/source/_posts/soa.md @@ -0,0 +1,265 @@ +--- +title: 【译】• 面向服务的架构 +date: 2019-06-19 21:20:40 +categories: + - [Translation] + - [SpringBoot] +tag: [SOA,SpringBoot] +--- + +在学习过程中,我们首先需要将学习知识的基本概念搞清楚,而搞清楚概念最权威的方式是查阅 **[英文版 • 维基百科](https://en.wikipedia.org)** ,或者是对应知识的官方文档上面查找相关的知识。这样学习才能学习到知识的精华,而不是阅读经过别人转译过的文章。因此,这篇文章仅是本人在学习 SOA 基本概念时,对维基百科知识的一个汇总翻译记录,不建议朋友把这篇文章当做你的学习资料,具体请查阅[Service-oriented architecture](https://en.wikipedia.org/wiki/Service-oriented_architecture)。 + +面向服务的架构(SOA)是一种[软件设计](https://en.wikipedia.org/wiki/Software_design)风格。 SOA 服务通过[应用组件](https://en.wikipedia.org/wiki/Application_components),通过网络[通信协议](https://en.wikipedia.org/wiki/Communications_protocol)的方式向其他组件提供服务。SOA 的基本原则是独立于厂商,独立于产品以及独立于技术[^1]。服务是一种功能的离散独立单元,可以远程访问并独立运行与更新,例如在线查询信用卡账单。 + + + +一个服务在诸多 SOA 定义中有 4 个属性[^2]: +1. 它逻辑上代表具有指定结果的业务活动 +2. 它是自包含的 +3. 它对于消费者来说是[黑盒](https://en.wikipedia.org/wiki/Black_box)(不可见) +4. 它可能包含其他基础服务[^3] + +不同的服务可以联合起来构建大型的[软件应用](https://en.wikipedia.org/wiki/Software_applications)[^4],SOA 遵循模块化[编程思想](https://en.wikipedia.org/wiki/Modular_programming),SOA 集成了分布式,独立维护和独立部署的软件组件,它通过技术和标准促使组件通过网络进行通信和协作,尤其是通过 IP 网络 + +## 概览 + +在 SOA 中,服务的使用是描述怎样通过[元数据](https://en.wikipedia.org/wiki/Metadata)[传递消息](https://en.wikipedia.org/wiki/Message_passing)和解析消息的协议。该元数据描述了服务的功能特性和服务质量特征。SOA 目标旨在允许用户将大块的功能组合在一起来去构成一个应用,形成纯粹由现有服务构建并以临时方式组合的应用程序。一个服务会向调用者提供一个简单的接口,它抽象出作为黑盒子的底层复杂性。其他用户可以在不了解其内部实现的情况下访问这些独立服务[^5]。 + +## 定义概念 + +相关的流行语服务导向促进了[面向服务](https://en.wikipedia.org/wiki/Service-orientation)间的[松耦合](https://en.wikipedia.org/wiki/Loose_coupling)。SOA 将功能分为不同的单元或服务,哪些开发人员可以通过网络访问,以便允许用户在应用程序的生产中组合和重用它们。这些服务及其相应的消费者通过明确定义的共享格式,传递数据或通过协调两个或更多服务之间的活动来互相通信 + +2009 年 10 月发布了关于 SOA 的宣言,其中提出了 6 个核心价值,如下所示 +1. **商业价值**比技术战略更重要 +2. **战略目标**比项目特定的利益更重要 +3. **内在的互操作**性比定制集成更重要 +4. **共享服务**比特定用途实现更重要 +5. **灵活性**比优化更重要 +6. **演进式**比追求初始化完美更重要 + +SOA 可看作是连续统一体的一部分,其范围从旧的[分布式计算](https://en.wikipedia.org/wiki/Distributed_computing)概念和[模块化编程](https://en.wikipedia.org/wiki/Modular_programming)[^6] [^9],通过 SOA,以及 [mashups](https://en.wikipedia.org/wiki/Mashup_(web_application_hybrid)),[SaaS](https://en.wikipedia.org/wiki/SaaS) 和[云计算](https://en.wikipedia.org/wiki/Cloud_computing)的当前实践(有些人认为是 SOA 的后代)[^10] + +## 原理 + +尽管许多行业已发布了自己的原则,但没有与 SOA 确切相关的行业标准,其中一些[^11] [^12] [^13] [^14]包括以下内容 + +### [标准地服务契约](https://en.wikipedia.org/wiki/Standardized_service_contract) + +服务遵循标准通信协议,有一组给定服务中的一个或多个服务描述文档共同定义 + +### [服务自治](https://en.wikipedia.org/w/index.php?title=Service_reference_autonomy&action=edit&redlink=1) + +服务之间的关系被最小化到它们只知道存在的等级 + +### [服务松耦合](https://en.wikipedia.org/w/index.php?title=Service_location_transparency&action=edit&redlink=1) + +无论网络位于何处,都可以从网络中的任何位置调用服务 + +### [服务长寿](https://en.wikipedia.org/w/index.php?title=Service_longevity&action=edit&redlink=1) + +服务应该设计为长寿,在可能的情况下,如果不需要新功能,服务应该避免强迫消费者进行更改。如果今天能调用的服务,到明天也应该能调用统一的服务 + +### [服务抽象化](https://en.wikipedia.org/wiki/Service_abstraction) + +服务充当黑盒,它们内在的逻辑对消费者是隐藏的 + +### [服务自治](https://en.wikipedia.org/wiki/Service_autonomy_principle) + +服务是独立的,从设计时和运行时的角度控制它们封装的功能 + +### [服务无状态化](https://en.wikipedia.org/wiki/Service_statelessness_principle) + +服务是无状态的,即返回请求的值或提供异常,从而最大限度的减少资源使用 + +### [服务粒度](https://en.wikipedia.org/wiki/Service_granularity_principle) + +确保服务具有足够的规模和范围的原则。服务向用户提供的功能必修三相关的 + +### 服务规范化 + +服务被分解或合并作为最小化冗余。在某些情况下,可能无法完成,这些是需要性能优化,访问和聚合的情况[^15] + +### [服务可组合性](https://en.wikipedia.org/wiki/Service_composability_principle) + +服务可用于组成其他服务 + +### [服务发现](https://en.wikipedia.org/wiki/Service_discovery) + +服务补充了交流元数据,通过它可以有效地发现和解释它们 + +### [服务可重用性](https://en.wikipedia.org/wiki/Service_reusability_principle) + +将逻辑分为多个服务,以促进代码的复用 + +### 服务[封装](https://en.wikipedia.org/wiki/Encapsulation_(computer_science)) + +许多最初未在 SOA 下计划的服务可能会被封装或成为 SOA 的一部分 + +## 模式 + +每个SOA构建块都可以扮演以下三种角色中的任何一种: + +### 服务提供者 + +它创建 Web 服务并将其信息提供给服务注册。每个提供者都会讨论大量的方法,以及为什么要公开哪些服务,哪些更重要:安全性或易用性,提供服务的价格等等。提供者还必须决定应该为给定的代理服务列出服务的类别[^16]以及使用该服务需要那种协议 + +### 服务代理,服务注册或服务存储 + +其主要功能是使用任何潜在的请求者都能获取有关 Web 服务的信息。实施的人决定代理的范围。公开的代理随处可见,但私有的代理只能向有限的公开代理开放。UDDI 是一种早期的,不再主动支持的 [Web 服务发现](https://en.wikipedia.org/wiki/Web_Services_Discovery)。 + +### 服务消费者 + +它使用各种查找操作在代理注册中查找,然后绑定到服务提供者以调用其中一个 Web 服务。无论服务消费者需要哪种服务,它们都必须通过代理,将其与相应的服务绑定,然后使用它。如果服务提供多种服务,它们可以访问多种服务。 + +服务消费者-提供者关系由[标准化服务契约](https://en.wikipedia.org/wiki/Standardized_service_contract)[^17]管理,其中包括业务部分,功能部分和技术部分。 + +[服务组合模式](https://en.wikipedia.org/wiki/Service_composability_principle)有两种广泛的高级架构风格:[服务编排](https://en.wikipedia.org/wiki/Service_choreography#Service_choreography_and_service_orchestration)。不受特定体系结构风格约束的较低级别的企业集成模式在 SOA 设计中任然具有相关性和合格性[^18] [^19] [^20]。 + +## 实现方法 + +SOA 可以通过 [Web 服务](https://en.wikipedia.org/wiki/Web_service)[^21]实现。这样做是为了使功能模块可以通过独立于平台和编程语言的标准协议访问。这些服务既可以代表新应用程序,也可以代表现有遗留系统的包装,使其具备网络功能。[^22] + +实现通用使用 Web 服务标准构建 SOA。一个例子是 [SOAP](https://en.wikipedia.org/wiki/SOAP),它在 2003 年从 W3C[^23] 推荐 1.2 版本后获得广泛的行业认可。这些标准(也称为 [Web 服务规范](https://en.wikipedia.org/wiki/List_of_web_service_specifications))还提供了更强的互操作性以及对锁定到专有供应商软件的一些保护。但是,也可以使用任何其他基于服务的技术(如 [Jini](https://en.wikipedia.org/wiki/Jini),[CORBA](https://en.wikipedia.org/wiki/CORBA) 或者 [REST](https://en.wikipedia.org/wiki/Representational_State_Transfer))实现 SOA + +架构可以独立于特定技术运行,因此可以使用多种技术实现,包括: +* 基于 WSDL 和 [SOAP](https://en.wikipedia.org/wiki/SOAP) 的 [Web 服务](https://en.wikipedia.org/wiki/Web_services) +* 消息传递,例如,使用 ActiveMQ, JMS, RabbitMQ +* RESTful HTTP,具有 [Representational 状态转移](https://en.wikipedia.org/wiki/Representational_state_transfer)(REST),构成自己的基于约束的架构风格 +* [OPC-UA](https://en.wikipedia.org/wiki/OPC_Unified_Architecture) +* [WCF](https://en.wikipedia.org/wiki/Windows_Communication_Foundation)(Microsoft 的 Web 服务实现,构成 WCF 的一部分) +* [Apache Thrift](https://en.wikipedia.org/wiki/Apache_Thrift) +* [gRPC](https://en.wikipedia.org/wiki/GRPC) +* [SORCER](https://en.wikipedia.org/wiki/SORCER) + +实现可以使用这些协议中的一个或多个,例如,可以使用文件系统机制来遵循符合 SOA 概念的进程间定义的接口规范来传递数据。关键是具有已定义接口的独立服务,可以调用它们以标准方式执行其任务,而无需预先知道调用应用程序的服务,并且没有应用程序具有或需要知道服务如何实际执行其任务。SOA 支持开发通过松耦合和可[互操作](https://en.wikipedia.org/wiki/Interoperable)的服务构建的应用程序 + +这些服务独立于底层平台和编程语言的正式定义(或契约,例如 WSDL)进行互操作。接口定义[隐藏了实施](https://en.wikipedia.org/wiki/Information_hiding)特定语言的服务实现。因此,基于 SOA 的系统可以独立于开发技术和平台(例如Java,.NET等)运行。例如,运行在.NET平台上的 C# 和 用 [JavaEE](https://en.wikipedia.org/wiki/Java_Platform,_Enterprise_Edition) 平台上运行的 Java 编写的服务都可以公共复合应用程序(或客户端)使用。在任一平台上运行的应用程序也可以使用在另一个平台上运行的服务作为重用的 Web 服务。托管环境还可以包括 COBOL 遗留系统并将其作为软件服务提供。[^24] + +诸如 [BPEL](https://en.wikipedia.org/wiki/BPEL) 之类的[高级编程语言](https://en.wikipedia.org/wiki/High-level_programming_language)以及诸如 [WS-CDL](https://en.wikipedia.org/wiki/WS-CDL) 和 [WS-Coordination](https://en.wikipedia.org/wiki/WS-Coordination) 之类的规范通过提供一种定义和支持将细粒度服务编排成更粗粒度的业务服务的方法来扩展服务的概念,架构师可以将其合并到[复合应用](https://en.wikipedia.org/wiki/Composite_applications)程序或[门户](https://en.wikipedia.org/wiki/Enterprise_portal)中实现的工作流和业务流中。 + +[面向服务的建模](https://en.wikipedia.org/wiki/Service-oriented_modeling)是一个 SOA 架构,可识别指导 SOA 从业者对其面向服务的资产进行概念化,分析,设计和构建的各种规程。[面向服务的建模框架(SOMF)](https://en.wikipedia.org/wiki/Service-oriented_modeling#Service-oriented_modeling_framework)提供了一种建模语言和一个工作架构映射,描述了有助于成功的面向服务的建模方法的各种组件。它说明了识别服务开发方案的“做什么”方面的主要元素。该模型使从业者能够制定[项目计划](https://en.wikipedia.org/wiki/Project_plan)并确定面向服务的计划的里程碑。SOMF 还提供了一种通用的建模符号,已解决业务和 IT 组织之间的一致性问题。 + +## 组织利益 + +一些[企业架构师](https://en.wikipedia.org/wiki/Enterprise_architect)认为,SOA 可以帮助企业更快,更经济地响应不断变化的市场条件。[^26]这种体系结构促进了宏(服务)级别的重用,而不是微(类)级别的重用。它还可以简化现有 IT(传统)资产的互联和使用。 + +![SOA的元素,由Dirk Krafzig,Karl Banke和Dirk Slama撰写 [^25]](https://res.cloudinary.com/incoder/image/upload/v1561944855/blog/elements_of_soa.png) + +使用SOA,我们的想法是组织可以从整体上看待问题。企业拥有更多地整体控制权。从理论上讲,不会有大量的开发人员使用任何工具集让他们满意,但他们将编码为业务中设定标准。他们还可以开发企业级 SOA,封装面向业务的基础架构。SOA 被描述为汽车驾驶员提供效率的高速公路系统。关键在于,如果每个人都有车,但在任何地方都没有高速公路,那么事情就会受到限制和混乱,无论是视图快速或有效地到达任何地方。IBM Web 服务副总裁 Michael Liebow 表示 SOA 是“建设的高速公路”。[^27] + +![SOA元模型,The Linthicum Group,2007](https://res.cloudinary.com/incoder/image/upload/v1561944919/blog/soa_meta-model.svg) + +在某些方面,SOA可以被视为架构演变而不是革命。它捕获了以前软件架构的许多[最佳实践](https://en.wikipedia.org/wiki/Best_practice)。例如,在通信系统中,很少开发使用真正静态绑定与网络中的其他设备通信的解决方案。通过采用 SOA 方法,此类系统可以将自己定位为强调定义明确,高度可互操作的接口的重要性。SOA 的其他前身包括[基于组件的软件工程](https://en.wikipedia.org/wiki/Component-based_software_engineering)和远程对象的面向对象分析和设计(OOAD),例如,在 [CORBA](https://en.wikipedia.org/wiki/CORBA) 中。 + +服务包括仅通过正式定义的页面可用的独立功能单元。服务可以是某种易于生产和改进的“纳米企业”。服务也可以是作为子下属服务的协调工作而构建的“大型企业”。SOA 的成熟部署有效地定义了组织的 API。 + +将服务实施视为大型项目的单独项目的原因包括: +1. 分离将业务概念推广到业务,即服务可以快速独立地从组织中常见的较大且移动较慢的项目中提供。业务开始了解回调服务的系统和简化的用户界面。这提倡[敏捷](https://en.wikipedia.org/wiki/Agility)。也就是说,它促进了业务创新并加快了产品上市时间[^28]。 +2. 分离促进了服务于消费项目的脱钩。这样可以鼓励良好的设计,因为服务的设计不需要知道消费者是谁。 +3. 服务的文档和测试文件未嵌入较大项目的详细信息中。当服务需要在后续需要重用时,这很重要。 + +SOA 承诺间接简化测试。服务是自治的,无状态的,具有完全记录的接口,并且与实现的关注点是分开的。如果组织拥有适当定义的测试数据,则会构建响应的存根,以便在构建服务时对测试数据做出反应。可以构建测试环境,其中原始和超出范围的服务是存根,而网格的其余ubuf 是完整服务的测试部署。由于每个接口都有完整的文档,并附有完整的回归测试文档,因此可以轻松识别测试服务中的问题。测试演变为仅仅验证测试服务是否根据其文档运行,并发现环境中所有服务的文档和测试用例存在的差距。管理[幂等](https://en.wikipedia.org/wiki/Agility)服务的数据状态是唯一的复杂性。 + +实例可能有助于将服务记录到有用的级别。Java community Process 中的一些 API 文档提供了良好的示例。由于这些是详尽无遗漏的,工作人员通常只使用重要的子集。JSR-89 的 ossjsa.pdf 文件中举例说明了这样做一个文件[^29]。 + +## 批评 + +SOA 已与 [Web 服务](https://en.wikipedia.org/wiki/Web_service)混淆[^30],但是,Web 服务只是实现构成 SOA 风格的模式的一种选择。在非本机或二进制形式的远程过程调用(RPC)的情况下,应用程序可能运行的更慢并且需要更多地处理能力,从而增加了成本。大多数实现都会产生这些开销,但 SOA 可以使用技术实现(例如,[Java Business Integration(JBI)](https://en.wikipedia.org/wiki/Java_Business_Integration),[Windows Communication Foundation(WCF)](https://en.wikipedia.org/wiki/Windows_Communication_Foundation)和 [data distribution service(DDS)](https://en.wikipedia.org/wiki/Data_distribution_service)),它们不依赖与远程过程调用或通过 XML 进行转换。与此同时,新兴的开源 XML 解析技术(如 [VTD-XML](https://en.wikipedia.org/wiki/VTD-XML))和各种 XML 兼容的二进制格式有望显著提高 SOA 性能。使用 [JSON](https://en.wikipedia.org/wiki/JSON) 而不是 XML 实现的服务不会受到这种性能的问题的影响[^31] [^32] [^33]。 + +有状态服务要求消费者和提供者共享相同的特定于消费者的上下文,该上下文包含在提供者和消费者之间交换的消息中或由其引入。如果服务提供者需要为每个消费者保留共享上下文,则此约束的缺点是它可能会降低服务提供者的整体可伸缩性。它还增加了服务提供者和消费者之间的耦合,使交换服务提供者更加困难[^34]。最终,一些批评者认为 SOA 服务仍然受到他们所代表的应用程序的限制[^35]。 + +SOA 的体系结构面临的主要挑战是管理元数据。基于 SOA 的环境包括许多彼此之间进行通信以执行任务的服务。由于设计可能涉及多个服务一起工作,因此应用程序可能会产生数百万条消息。进一步的服务可能属于不同的组织甚至是竞争公司,造成巨大的信任问题。因此 SOA 治理进入了事务的计划[^36]。 + +SOA 面临的另一个主要问题是缺乏统一的测试架构。没有工具可以提供在 SOA 的体系结构中测试这些服务所需的功能。困难的主要原因是:[^37] +* 异质性和解决方案的复杂性 +* 由于自主服务的集成,大量的测试组合 +* 包含来自不同和竞争供应商的服务 +* 由于新功能和服务的可用性,[平台](https://en.wikipedia.org/wiki/Platform_as_a_service)不断变化 + +请参阅 drops.dagstuhl.de[^38],了解有关[软件服务工程](https://en.wikipedia.org/wiki/Service-oriented_modeling)的其他挑战,部分解决方案和研究路线图 + +## 扩展和变体 + +### 事件驱动的体系结构 + +主要文章:[事件驱动的架构](https://en.wikipedia.org/wiki/Event-driven_architecture) + +### Web2.0 + +[Tim O'Reilly](https://en.wikipedia.org/wiki/Tim_O%27Reilly) 创造了“[Web2.0](https://en.wikipedia.org/wiki/Web_2.0)”一词来描述一种快速增长的基于网络的应用程序[^39]。经历了广泛报道的主题涉及 Web2.0与 SOA 体系机构之间的关系。 + +SOA 是将应用程序逻辑封装在具有统一定义的接口的服务中并通过发现机制公开可用的哲学。复杂性-隐藏和重用的概念,以及松耦合服务的概念,激发了研究人员详细阐述两种哲学,SOA 和 Web2.0及其各自应用之间的相似性。一些人认为 Web2.0和 SOA 具有显著不同的元素,因此不能被视为“平行哲学”,而其他人认为这两个概念是互补的,并将 Web2.0视为全球 SOA[^40]。 + +Web2.0和 SOA 的理念满足了不同的用户需求,从而暴露了设计方面的差异以及实际应用中使用的技术。但是,截止 2008 年,用例展示了结合 Web2.0和 SOA 技术和原则的潜力[^40]。 + +### 微服务 + +主要文章:[微服务](https://en.wikipedia.org/wiki/Microservices) + +微服务是对用于构建[分布式软件系统](https://en.wikipedia.org/wiki/Distributed_computing)的面向服务的体系结构的现代解释。微服务架构[^41]中的服务是通过[网络](https://en.wikipedia.org/wiki/Computer_network)互相通信以实现目标的[过程](https://en.wikipedia.org/wiki/Process_(computing))。这些服务使用技术不可知[协议](https://en.wikipedia.org/wiki/Communications_protocol)[^42],这有助于封装语言和框架的选择,使他们的选择成为服务内部的一个问题。微服务是 SOA 的一种新的实现方法,自 2014 年(以及 [DevOps](https://en.wikipedia.org/wiki/DevOps) 推出以后)开始流行,并且强调持续部署和其他的=敏捷实践[^43]。 + +微服务没有一个共同商定的定义。在文献中可以找到以下特征和原理: +* 细粒度接口(可独立部署的服务) +* 业务驱动的开发(例如域驱动设计) +* IDEAL 云应用架构 +* 多语言编程和持久化存储 +* 轻量级容器部署 +* 分散的持续交付 +* DevOps 提供全面的服务监控 + +## 其他 + +* [松耦合](https://en.wikipedia.org/wiki/Loose_coupling) +* [OASIS SOA 参考模型](https://en.wikipedia.org/wiki/OASIS_SOA_Reference_Model) +* [服务粒度原则](https://en.wikipedia.org/wiki/Service_granularity_principle) +* [SOA 治理](https://en.wikipedia.org/wiki/SOA_governance) +* [软件架构](https://en.wikipedia.org/wiki/Software_architecture) +* [面向服务的通信](https://en.wikipedia.org/wiki/Service-oriented_communications) +* [面向服务的应用程序开发](https://en.wikipedia.org/wiki/Service-oriented_development_of_applications) +* [面向服务的分布式应用程序](https://en.wikipedia.org/wiki/Service-oriented_distributed_applications) + +## 参考 + +[^1]: "[Chapter 1: Service Oriented Architecture (SOA)](https://web.archive.org/web/20160206132542/https://msdn.microsoft.com/en-us/library/bb833022.aspx)". msdn.microsoft.com. Archived from [the original](https://msdn.microsoft.com/en-us/library/bb833022.aspx) on February 6, 2016. Retrieved September 21, 2016. +[^2]: "[Service-Oriented Architecture Standards - The Open Group](https://publications.opengroup.org/standards/soa)". www.opengroup.org. +[^3]: "[What Is SOA?](https://web.archive.org/web/20160819141303/http://opengroup.org/soa/source-book/soa/soa.htm)". www.opengroup.org. Archived from [the original](http://www.opengroup.org/soa/source-book/soa/soa.htm) on August 19, 2016. Retrieved September 21, 2016. +[^4]: Velte, Anthony T. (2010). Cloud Computing: A Practical Approach. McGraw Hill. [ISBN](https://en.wikipedia.org/wiki/International_Standard_Book_Number) [978-0-07-162694-1](https://en.wikipedia.org/wiki/Special:BookSources/978-0-07-162694-1). +[^5]: "[Migrating to a service-oriented architecture, Part 1](https://web.archive.org/web/20081209120916/http://www-128.ibm.com/developerworks/library/ws-migratesoa/)". December 9, 2008. Archived from the original on December 9, 2008. Retrieved September 21, 2016. +[^6]: Michael Bell (2008). "Introduction to Service-Oriented Modeling". Service-Oriented Modeling: Service Analysis, Design, and Architecture. Wiley & Sons. p. 3. [ISBN](https://en.wikipedia.org/wiki/International_Standard_Book_Number) [978-0-470-14111-3](https://en.wikipedia.org/wiki/Special:BookSources/978-0-470-14111-3). +[^7]: Michael Bell (2010). SOA Modeling Patterns for Service-Oriented Discovery and Analysis. Wiley & Sons. p. 390. [ISBN](https://en.wikipedia.org/wiki/International_Standard_Book_Number) [978-0-470-48197-4](https://en.wikipedia.org/wiki/Special:BookSources/978-0-470-48197-4). +[^8]: "[SOA Manifesto](http://www.soa-manifesto.org/)". www.soa-manifesto.org. Retrieved September 21, 2016. +[^9]: Thomas Erl (June 2005). About the Principles. Serviceorientation.org +[^10]: "[Application Platform Strategies Blog: SOA is Dead; Long Live Services](http://apsblog.burtongroup.com/2009/01/soa-is-dead-long-live-services.html)". Apsblog.burtongroup.com. January 5, 2009. Retrieved August 13, 2012. +[^11]: Yvonne Balzer [Improve your SOA project plans](http://www-128.ibm.com/developerworks/webservices/library/ws-improvesoa/), IBM, July 16, 2004 +[^12]: Microsoft Windows Communication Foundation team (2012). "[Principles of Service Oriented Design](http://msdn.microsoft.com/en-us/library/bb972954.aspx)". msdn.microsoft.com. Retrieved September 3, 2012. +[^13]: Principles by [Thomas](https://en.wikipedia.org/wiki/Thomas_Erl) Erl of SOA Systems Inc. [eight specific service-orientation principles](http://soaprinciples.com) +[^14]: M. Hadi Valipour; Bavar AmirZafari; Kh. Niki Maleki; Negin Daneshpour (2009). "A brief survey of software architecture concepts and service oriented architecture". 2009 2nd IEEE International Conference on Computer Science and Information Technology. pp. 34–38. [doi:10.1109/ICCSIT.2009.5235004](https://doi.org/10.1109%2FICCSIT.2009.5235004). [ISBN](https://en.wikipedia.org/wiki/International_Standard_Book_Number) [978-1-4244-4519-6](https://en.wikipedia.org/wiki/Special:BookSources/978-1-4244-4519-6). +[^15]: Tony Shan (2004). "Building a service-oriented e Banking platform". IEEE International Conference on Services Computing, 2004. (SCC 2004). Proceedings. 2004. pp. 237–244. [doi:10.1109/SCC.2004.1358011](https://doi.org/10.1109%2FSCC.2004.1358011). [ISBN](https://en.wikipedia.org/wiki/International_Standard_Book_Number) [978-0-7695-2225-8.2004](https://en.wikipedia.org/wiki/Special:BookSources/978-0-7695-2225-8) +[^16]: Duan, Yucong; Narendra, Nanjangud; Du, Wencai; Wang, Yongzhi; Zhou, Nianjun. "[Exploring Cloud Service Brokering from an Interface Perspective](http://ieeexplore.ieee.org/document/6928915/)". [IEEE](https://en.wikipedia.org/wiki/IEEE). +[^17]: Duan, Yucong. "[A Survey on Service Contract](http://ieeexplore.ieee.org/document/6299375/)". [IEEE](https://en.wikipedia.org/wiki/IEEE). +[^18]: Olaf Zimmermann, Cesare Pautasso, Gregor Hohpe, Bobby Woolf (2016). "[A Decade of Enterprise Integration Patterns](http://ieeexplore.ieee.org/document/7368007/)". IEEE Software. 33 (1): 13–19. [doi](https://en.wikipedia.org/wiki/Digital_object_identifier):[10.1109/MS.2016.11](https://doi.org/10.1109%2FMS.2016.11). +[^19]: Rotem-Gal-Oz, Arnon (2012). SOA Patterns. Manning Publications. [ISBN](https://en.wikipedia.org/wiki/International_Standard_Book_Number) [978-1933988269](https://en.wikipedia.org/wiki/Special:BookSources/978-1933988269). +[^20]: K. Julisch et al., [Compliance by Design – Bridging the Chasm between Auditors and IT Architects](http://soadecisions.org/download/ComplianceByDesign-AAM.pdf). Computers & Security, Elsevier. Volume 30, Issue 6-7, Sep.-Oct. 2011. +[^21]: Brandner, M., Craes, M., Oellermann, F., Zimmermann, O., Web Services-Oriented Architecture in Production in the Finance Industry, Informatik-Spektrum 02/2004, Springer-Verlag, 2004 +[^22]: "[www.ibm.com](http://www.ibm.com/support/knowledgecenter/en/SSEQTP_6.1.0/com.ibm.websphere.base.iseries.doc/info/iseries/ae/cwbs_soawbs.html)". Retrieved September 10, 2016. +[^23]: "[SOAP Version 1.2 の公開について (W3C 勧告)](http://www.w3.org/2003/06/soap12-pressrelease)" (in Japanese). W3.org. Retrieved August 13, 2012. +[^24]: Okishima, Haruhiru (2006). ". ["Case Study of System Architecture that use COBOL assets"](http://www.fujitsu.com/global/documents/about/resources/publications/fstj/archives/vol42-3/paper18.pdf)" (PDF). +[^25]: Enterprise SOA. Prentice Hall, 2005 +[^26]: Christopher Koch [A New Blueprint For The Enterprise](http://www.cio.com.au/index.php/id;1350140708), CIO Magazine, March 1, 2005 +[^27]: Elizabeth Millard (January 2005). "Building a Better Process". Computer User. Page 20. +[^28]: Brayan Zimmerli (November 11, 2009) [Business Benefits of SOA](https://web.archive.org/web/20101105063545/http://www.brayan.com/projects/BenefitsOfSOA/default.htm), University of Applied Science of Northwestern Switzerland, School of Business +[^29]: J[SR-000089 OSS Service Activation API Specification 1.0 Final Release](https://web.archive.org/web/20110726070810/https://cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site/en_US/-/USD/ViewProductDetail-Start?ProductRef=7854-oss_service_activation-1.0-fr-spec-oth-JSpec%40CDS-CDS_Developer). sun.com +[^30]: Joe McKendrick. "[Bray: SOA too complex; 'just vendor BS'](http://www.zdnet.com/blog/service-oriented/bray-soa-too-complex-just-vendor-bs/597)". ZDNet. +[^31]: Jimmy Zhang (February 20, 2008) "[Index XML Documents with VTD-XML](http://xml.sys-con.com/read/453082.htm)". XML Journal. +[^32]: Jimmy Zhang (August 5, 2008) "[i-Technology Viewpoint: The Performance Woe of Binary XML](http://soa.sys-con.com/read/250512.htm)". Microservices Journal. +[^33]: Jimmy Zhang (January 9, 2008) "[Manipulate XML Content the Ximple Way](http://www.devx.com/xml/Article/36379)". devx.com. +[^34]: "[The Reason SOA Isn't Delivering Sustainable Software](http://www.jpmorgenthal.com/morgenthal/?p=31)". jpmorgenthal.com. June 19, 2009. Retrieved June 27, 2009. +[^35]: "[SOA services still too constrained by applications they represent](http://www.zdnet.com/article/soa-services-still-too-constrained-by-applications-they-represent/)". zdnet.com. June 27, 2009. Retrieved June 27, 2009. +[^36]: "[Governance Layer](https://www.opengroup.org/soa/source-book/soa_refarch/governance.htm)". www.opengroup.org. Retrieved September 22, 2016. +[^37]: "[How to Efficiently Test Service Oriented Architecture | WSO2 Inc](http://wso2.com/library/articles/2014/04/how-to-efficiently-test-service-oriented-architecture/)". wso2.com. Retrieved September 22, 2016. +[^38]: http://drops.dagstuhl.de/opus/volltexte/2009/2046/pdf/09021_abstracts_collection.2046.pdf +[^39]: "[What Is Web 2.0](http://www.oreillynet.com/pub/a/oreilly/tim/news/2005/09/30/what-is-web-20.html)". Tim O'Reilly. September 30, 2005. Retrieved June 10, 2008. +[^40]: Christoph Schroth & Till Janner (2007). "[Web 2.0 and SOA: Converging Concepts Enabling the Internet of Services](http://www.alexandria.unisg.ch/Publikationen/37270)". IT Professional 9 (2007), Nr. 3, pp. 36–41, IEEE Computer Society. Retrieved February 23, 2008. +[^41]: Dragoni, Nicola; Giallorenzo, Saverio; Alberto Lluch Lafuente; Mazzara, Manuel; Montesi, Fabrizio; Mustafin, Ruslan; Safina, Larisa (2016). "Microservices: yesterday, today, and tomorrow". [arXiv](https://en.wikipedia.org/wiki/ArXiv):[1606.04036v1](https://arxiv.org/abs/1606.04036v1) [cs.SE](https://arxiv.org/archive/cs.SE). +[^42]: James Lewis and Martin Fowler. "[Microservices](http://martinfowler.com/articles/microservices.html)". +[^43]: Balalaie, A.; Heydarnoori, A.; Jamshidi, P. (May 1, 2016). "[Microservices Architecture Enables DevOps: Migration to a Cloud-Native Architecture](http://ieeexplore.ieee.org/lpdocs/epic03/wrapper.htm?arnumber=7436659)". IEEE Software. 33 (3): 42–52. [doi](https://en.wikipedia.org/wiki/Digital_object_identifier):[10.1109/MS.2016.64](https://doi.org/10.1109%2FMS.2016.64). [hdl](https://en.wikipedia.org/wiki/Handle_System):[10044/1/40557](https://hdl.handle.net/10044%2F1%2F40557). [ISSN](https://en.wikipedia.org/wiki/International_Standard_Serial_Number) [0740-7459](https://www.worldcat.org/issn/0740-7459). diff --git a/source/_posts/springboot1.md b/source/_posts/springboot1.md new file mode 100644 index 000000000..5b183f9d6 --- /dev/null +++ b/source/_posts/springboot1.md @@ -0,0 +1,160 @@ +--- +title: SpringBoot(一) 初识 +date: 2019-06-23 09:10:11 +categories: SpringBoot +tag: [SpringBoot] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1561900597/blog/springboot.jpg) + + + +从本篇文章开始,记录学习 SpringBoot 框架在实践,源码方面的知识,本节是第一篇,因此不涉及相关复杂知识的学习。众所周知,随着微服务的广泛流行,Spring 系列的 SpringBoot 和 SpringCloud 的应用也更受欢迎,那么请跟随我的脚本来一步步解开 SpringBoot 她神秘的面纱 + +熟悉后端服务开发的小伙伴,在使用 SpringBoot 时一定会有这样的感受,咦,以前繁琐的配置,现在都不用再去配置一大堆东西了,以前跑起来一个 demo,感觉真是千辛万苦,错一步就 game over,以前服务基本都是已 war 包的形式运行在 Tomcat 中,而现在,你基本不需要手动写太多的代码,一个应用服务就可以运行起来,其次现在应用基本已 jar 包方式直接运行,虽然本质还是运行在 Tomcat 中,但现在 jar 包中已经有了服务运行的基础环境,可以直接使用 jar 相关的运行命令就可以运行起服务。好了,废话了这么多,先看看我们如何运行起一个 DEMO 应用。 + +## 环境及版本 + +* SpringBoot Version:2.1.6.RELEASE +* System:macOS Mojave +* JDK Version:1.8 +* Gradle:5.4.1 +* IDE:IntelliJ IDEA + +>本系列应用使用如上环境,其次应用包管理,小伙伴可以选择自己熟悉的 Maven 进行管理,而这里都使用 Gradle 进行管理 + +## Demo + +### [Spring Initializr](https://start.spring.io) + +为了让开发者快速上手,官方提供了一建生成 SpringBoot 项目,你按需选择你需要的依赖即可。操作步骤如下截图 +![spring-initializ](https://res.cloudinary.com/incoder/image/upload/v1561906733/blog/spring-initializr.png) + +### IDEA Init + +![](https://res.cloudinary.com/incoder/image/upload/v1616526451/blog/spring-init.png) + +IDEA分为四步完成初始 + +1. 选择 Spring Initializr 初始化向导 +2. 填写项目坐标信息,构建工具,版本,报名等 +3. 选择需要的组件(会自动添加依赖) +4. 选择项目存放路径 + +## Spring 运行 + +### 命令 + +#### macOS or Linux + +```bash +# 项目路径下(spring-start) +gradlew bootRun +``` + +#### Windows + +```bash +# 项目路径下(spring-start) +./gradlew bootRun +``` + +### 运行说明 + +![spring-running-logo](https://res.cloudinary.com/incoder/image/upload/v1562167001/blog/spring-running-logo.png) + +## Spring 打包 + +### jar 分析 + +![springboot-deploy-jar-unzip](https://res.cloudinary.com/incoder/image/upload/v1561259381/blog/springboot-deploy-jar-unzip.png) + +目录说明 +``` +project/ +├── BOOT-INF/ +│ ├── classes # 当前项目结果文件放置在 classes 路径下 +│ │ │ └── application.properties # 项目中配置文件 +│ │ ├── org/ # 项目中 java 路径下,编译成 class 文件路径 +│ │ ├── static/ # 项目中 resources 路径下的静态文件夹 +│ │ └── templates/ # 项目中 resources 路径下的模板文件夹 +│ └── lib/ # 项目所依赖的第三方 jar(Tomcat,SpringBoot 等) +├── META-INF/ +│ └── MANIFEST.MF # 清单文件,用于描述可执行 jar 的一些基本信息 +└── org/springframework/boot/loader/ # jar 包启动相关的引导 + ├── archive/ + ├── data + ├── ExectableArchiveLauncher.class + ├── jar/ + ├── JarLauncher.class + ├── LaunchedURLClassLoader.class + ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class + ├── Launcher.class + ├── MainMethodRunner.class + ├── PropertiesLauncher.class + ├── PropertiesLauncher$1.class + ├── PropertiesLauncher$ArchiveEntryFilter.class + ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class + ├── PropertiesLauncher$ArchiveEntryFilter.class + ├── util/ + └── WarLauncher.class +``` + +#### MANIFEST.MF + +```jar +Manifest-Version: 1.0 # 清单版本号 +Start-Class: org.incoder.start.SpringbootStartApplication # 项目 main 方法所在的类 +Spring-Boot-Classes: BOOT-INF/classes/ # 项目相关代码在打包后 jar 中的路径 +Spring-Boot-Lib: BOOT-INF/lib/ # 项目中所依赖的第三方 jar 在打包后 jar 中的路径 +Spring-Boot-Version: 2.1.6.RELEASE # 项目 SpringBoot 版本 +Main-Class: org.springframework.boot.loader.JarLauncher # 当前 jar 文件的执行入口类(main 方法所在的类) +回车换行(在清单文件中,必须有,否则会出错) +``` + +#### org/springframework/……目录 + +项目中引入的第三方 jar 中并不包含`org/springframework/boot/loader`内容,那这个目录是从哪里来的呢? + +寻找最终发现是项目中我们的`build.gradle`文件中,引入的`org.springframework.boot:spring-boot-gradle-plugin`依赖,而这个依赖位于`classpath`下,说明引入的这个插件 **仅仅** 是在项目构建时才起作用,当项目进行打包后,并不会把插件包打入到项目的依赖库中,也就是`BOOT-INF/lib/`路径下 + +如何去研究在`org/springframework/boot/loader`下的源码内容呢? +最好的方式是在项目的依赖中导入`org.springframework.boot:spring-boot-loader`依赖 + +>原则上,在项目开发过程中是不需要引入`org.springframework.boot:spring-boot-loader`依赖,这里只是为了方便阅读源码进行学习 + +## Spring 其他 + +### 配置文件格式 + +* [properties](https://en.wikipedia.org/wiki/.properties) +* **推荐** [yml](https://en.wikipedia.org/wiki/YAML) + +>配置文件学习可参考 [SpringBoot(四)配置文件](https://incoder.org/2019/07/28/springboot4/) + +### 常用命令 + +#### gradle tasks + +表示获取当前工程可用的 gradle tasks 命令 + +##### Application tasks + +* bootRun:Runs this project as a Spring Boot application.(以 bootJar 的形式运行当前项目) + +##### Build tasks + +* bootJar:Assembles an executable jar archive containing the main classes and their dependencies.(装配一个可执行的 jar(自包含的 jar 包,不依赖其他容器) 归档,这个归档 jar 中包含了所需的依赖以及主类等) + +##### Run jar + +```bash +java -jar jar-name.jar +``` + +##### Other + +```bash +# 解压 jar 到当前 start 目录下 +unzip start-0.0.1-SNAPSHOT.jar -d ./start +``` \ No newline at end of file diff --git a/source/_posts/springboot10.md b/source/_posts/springboot10.md new file mode 100644 index 000000000..8c2d167d3 --- /dev/null +++ b/source/_posts/springboot10.md @@ -0,0 +1,20 @@ +--- +title: SpringBoot(十)Mybatis 常用标签 +date: 2020-03-24 10:24:00 +categories: SpringBoot +tag: [SpringBoot, Mybatis] +--- + +![](https://res.cloudinary.com/incoder/image/upload/v1585186336/blog/MyBatis.jpg) + + + +关于 Mybatis 作为在国内普遍使用的 ORM 框架,我们在使用上要掌握常用的标签 + +![mybatis-label](https://res.cloudinary.com/incoder/image/upload/v1585238573/blog/mybatis-label.png) + +## 附录 + +* [官方教程《XML 映射器》](https://mybatis.org/mybatis-3/zh/sqlmap-xml.html) +* [官方教程《动态 SQL》](https://mybatis.org/mybatis-3/zh/dynamic-sql.html) +* [Mybatis常用标签](https://www.cnblogs.com/zjfjava/p/8886432.html) \ No newline at end of file diff --git a/source/_posts/springboot11.md b/source/_posts/springboot11.md new file mode 100644 index 000000000..b1a344add --- /dev/null +++ b/source/_posts/springboot11.md @@ -0,0 +1,30 @@ +--- +title: SpringBoot 源码构建 +date: 2020-12-31 10:24:00 +categories: SpringBoot +tag: [SpringBoot, Gradle] +--- + +前两天刚刚学习了 Gradle 构建 SpringBoot 项目,再查看官方文档时,得知 SpringBoot 从 [Spring Boot 2.3.0.M1](https://spring.io/blog/2020/06/08/migrating-spring-boot-s-build-to-gradle) 版本开始完全切换到使用 Gradle 来构建项目,那么本篇文章就来实践,基于源码来编译构建 SpringBoot,话不多说,本次构建构建是 2020 年的最后一次发布的版本 2.4.1 + + + +## 环境 + +* OS:macOS 11.1 +* JDK:JDK1.8 +* Gradle:6.7.1-bin +* IDE:IntelliJ IDEA Community 2020.3 + +Gradle 版本通过 https://github.com/spring-projects/spring-boot/blob/master/gradle/wrapper/gradle-wrapper.properties 文件可知,使用的 6.7.1-bin,那么本地也使用该版本编译,对于 Gradle 的安装可参考 [Gradle(一)基础](https://incoder.org/2020/12/10/gradle1/#Gradle-安装配置) 文章 + +## 获取源码 + +```bash +# 这里使用 cnpmjs 来提高 clone 速度 +git clone https://github.com.cnpmjs.org/spring-projects/spring-boot.git +``` + +## 编译构建 + +使用 IDEA 打开项目,会自动创建索引以及,下载项目的依赖,由于依赖的 jar 比较多,建议使用 [阿里云](https://maven.aliyun.com/) 镜像,关于 Gradle 怎么修改依赖镜像源,可参考 [专治各种网络不服](https://incoder.org/2020/02/27/fuck-gfw/#Gradle) 文章,阿里云镜像能加速大部分的 jar,但有一部分在阿里云上并没有,你可以通过手动方式导入到本地 \ No newline at end of file diff --git a/source/_posts/springboot2.md b/source/_posts/springboot2.md new file mode 100644 index 000000000..53bd0e971 --- /dev/null +++ b/source/_posts/springboot2.md @@ -0,0 +1,404 @@ +--- +title: SpringBoot(二) 启动分析JarLauncher +date: 2019-07-05 11:10:11 +categories: SpringBoot +tag: [SpringBoot] +--- + +我们在开发过程中,使用 {% label info@java -jar you-jar-name.jar %} 命令来启动应用,它是如何启动?以及它如何去寻找 `.class` 文件并执行这些文件?本节就带着这两个问题,让我们一层层解开 SpringBoot 项目的 jar 启动过程,废话不多说,跟着我的脚步一起去探索 {% label danger@spring-boot-load %} 的秘密。 + +在 [SpringBoot(一)初识](https://incoder.org/2019/06/23/springboot1/) 已经解释了为什么在编译后的 jar 中根目录存在 **org/springframework/boot/loader** 内容,以及为了方便学习研究,我们需要在项目的依赖中导入 {% label success@org.springframework.boot:spring-boot-loader %} 依赖。同时我们在解压的 {% label info@you-jar-name.jar %} 文件中,查看对应的清单文件 {% label primary@MANIFEST.MF %} 内容,其中明确指出了应用的入口 **{% label @org.springframework.boot.loader.JarLauncher %}** 因此我们就从 **JarLauncher** 开始一步步深入 + + + +![spring-boot-loader-jarlauncher](https://res.cloudinary.com/incoder/image/upload/v1562394534/blog/spring-boot-loader-jarlauncher.png) + +## 结构 + +先用Diagrams来表述 **JarLauncher** 类之间的结构及方法等相关信息 +![jarlauncher](https://res.cloudinary.com/incoder/image/upload/v1562399159/blog/jarlauncher.png) + +从Diagrams可知 +* 继承关系:JarLauncher extends ExecutableArchiveLauncher extends Launcher +* 启动入口:JarLauncher {% label success@main %} 方法 + +>关于图上图标含义,这里就不再赘述,烦请移步 [IntelliJ IDEA Icon reference](https://www.jetbrains.com/help/idea/symbols.html) + +## 流程分析 + +### jar规范 + +对于 Java 标准的 jar 文件来说,规定在一个 jar 文件中,我们必须要将指定 {% label success@main.class %} 的类直接放置在文件的顶层目录中(也就是说,它不予许被嵌套),否则将无法加载,对于 BOOT-INF/class/ 路径下的 `class` 因为不在顶层目录,因此也是无法直接进行加载, 而对于 BOOT-INF/lib/ 路径的 jar 属于嵌套的(Fatjar),也是不能直接加载,因此 Spring 要想启动加载,就需要自定义实现自己的类加载器去加载。 + +>关于 jar **官方标准**说明请移步 +>* [JAR File Specification](https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File) +>* [JAR (file format)](https://en.wikipedia.org/wiki/JAR_(file_format)) + +### 源码分析 + +#### main 方法 + +根据清单文件 {% label primary@MANIFEST.MF %} 中 {% label warning@Main-Class %} 的描述,我们知道入口类就是 **JarLauncher**;先看下这个类的 javadoc 介绍 + +```java +/** + * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are + * included inside a {@code /BOOT-INF/lib} directory and that application classes are + * included inside a {@code /BOOT-INF/classes} directory. + * + * 用于基于JAR的归档。这个启动程序假设依赖jar包含在{@code /BOOT-INF/lib}目录中, + * 应用程序类包含在{@code /BOOT-INF/classes}目录中 + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +``` + +紧接着,要进行源码分析,那肯定是找到入口,一步步深入,那么对于 **JarLauncher** 就是它的 {% label success@main %} 方法了 + +```java +public static void main(String[] args) throws Exception { + // launch 方法是调用父类 Launcher 的 launch 方法 + new JarLauncher().launch(args); +} +``` + +那我们去看一看 Launcher 的 {% label success@launch %} 方法 + +```java +/** + * Launch the application. This method is the initial entry point that should be + * called by a subclass {@code public static void main(String[] args)} method. + * + * 启动一个应用,这个方法应该被初始的入口点,这个入口点应该是一个Launcher的子类的 + * public static void main(String[] args)这样的方法调用 + * + * @param args the incoming arguments + * @throws Exception if the application fails to launch + */ +protected void launch(String[] args) throws Exception { + // 1. 注册一些 URL的属性 + JarFile.registerUrlProtocolHandler(); + // 2. 创建类加载器(LaunchedURLClassLoader),加载得到集合要么是BOOT-INF/classes/ + // 或者BOOT-INF/lib/的目录或者是他们下边的class文件或者jar依赖文件 + ClassLoader classLoader = createClassLoader(getClassPathArchives()); + // 3. 启动给定归档文件和完全配置的类加载器的应用程序 + launch(args, getMainClass(), classLoader); +} +``` + +#### getClassPathArchives 方法 + +{% label success@launch %} 方法的第一步的相关内容比较简单,这里不做过多说明,主要后面两步,我们先看第二步,创建一个类加载器(ClassLoader),其中 getClassPathArchives() 方法是一个抽象方法,具体的实现有(ExecutableArchiveLauncher 和 {% label default@PropertiesLauncher %} ,因为我们研究的 JarLauncher 是继承 ExecutableArchiveLauncher ,因此我们这里看 ExecutableArchiveLauncher 类中 getClassPathArchives() 方法的实现)我们要看看这个方法中它做了什么 + +```java +@Override +protected List getClassPathArchives() throws Exception { + // 得到一个Archive的集合(BOOT-INF/classes/)和(BOOT-INF/lib/)目录所有的文件 + // a. this.archive 中当前类的 archive 是怎么来的? + // b. getNestedArachives()是如何获得一个嵌套的 jar 归档? + // c. this::isNestedArchive 这个方法引用它做了什么? + List archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive)); + // 一个事后处理的方法 + postProcessClassPathArchives(archives); + return archives; +} +``` + +{% label primary@this.archive %} 位于当前类 ExecutableArchiveLauncher 的构造方法中 + +```java +public ExecutableArchiveLauncher() { + try { + // 调用 createArchive() 方法得到Archive + this.archive = createArchive(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } +} + +///////////////////////////////////////////////////////////// +// 紧接着我们查看 createArchive() 方法都做了什么 // +///////////////////////////////////////////////////////////// + +// Launcher.class 中的 createArchive()方法 +// 得到我们运行文件的Archive相关的信息 +protected final Archive createArchive() throws Exception { + ProtectionDomain protectionDomain = getClass().getProtectionDomain(); + CodeSource codeSource = protectionDomain.getCodeSource(); + URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null; + String path = (location != null) ? location.getSchemeSpecificPart() : null; + if (path == null) { + throw new IllegalStateException("Unable to determine code source archive"); + } + // 返回我们要执行的jar文件的绝对路径(java -jar xxx.jar中 xxx.jar的绝对路径) + File root = new File(path); + if (!root.exists()) { + throw new IllegalStateException("Unable to determine code source archive from " + root); + } + return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); +} +``` + +对于 getNestedArachives() 方法,它是 Archive 的接口 + +```java +/** + * Returns nested {@link Archive}s for entries that match the specified filter. + * + * 返回与过滤器相匹配的嵌套归档文件 + * + * @param filter the filter used to limit entries + * @return nested archives + * @throws IOException if nested archives cannot be read + */ +List getNestedArchives(EntryFilter filter) throws IOException; + +///////////////////////////////////////////////////////////// +// 紧接着我们查看 getNestedArchives() 的实现 // +///////////////////////////////////////////////////////////// + +// 这里的参数 EntryFilter类型中有一个 matches(Entry entry) 方法, +// 这也是this::isNestedArchive所对应的实际方法 +@Override +public List getNestedArchives(EntryFilter filter) throws IOException { + List nestedArchives = new ArrayList<>(); + for (Entry entry : this) { + if (filter.matches(entry)) { + nestedArchives.add(getNestedArchive(entry)); + } + } + return Collections.unmodifiableList(nestedArchives); +} +``` + +而 {% label primary@this::isNestedArchive %} 方法引用,我们查看 `isNestedArchive` 抽象方法 + +```java +/** + * Determine if the specified {@link JarEntry} is a nested item that should be added + * to the classpath. The method is called once for each entry. + * + * 确定指定的{@link JarEntry}是否是应该添加到类路径的嵌套项。对每个条目调用该方法一次 + * + * @param entry the jar entry + * @return {@code true} if the entry is a nested item (jar or folder) + */ +protected abstract boolean isNestedArchive(Archive.Entry entry); + +///////////////////////////////////////////////////////////// +// 紧接着我们查看 isNestedArchive() 实现 // +///////////////////////////////////////////////////////////// + +// JarLauncher.class 中的 isNestedArchive()方法 +@Override +protected boolean isNestedArchive(Archive.Entry entry) { + // 如果是目录判断是不是BOOT-INF/classes/目录 + if (entry.isDirectory()) { + return entry.getName().equals(BOOT_INF_CLASSES); + } + // 如果是文件判断文件的前缀是不是BOOT-INF/lib/开头 + return entry.getName().startsWith(BOOT_INF_LIB); +} +``` + +#### createClassLoader 方法 + +把符合条件的 Archives 作为参数传入到 createClassLoader() 方法,创建一个类加载器,我们跟进去,查看 createClassLoader() 方法 + +```java +/** + * Create a classloader for the specified archives. + * + * 创建一个所指定归档文件的类加载器 + * + * @param archives the archives + * @return the classloader + * @throws Exception if the classloader cannot be created + */ +protected ClassLoader createClassLoader(List archives) throws Exception { + List urls = new ArrayList<>(archives.size()); + // 遍历传进来的 archives,将每一个 Archive 的 URL(归档文件在磁盘上的完整路径)添加到 urls 集合中 + for (Archive archive : archives) { + urls.add(archive.getUrl()); + } + // + return createClassLoader(urls.toArray(new URL[0])); +} + + +/** + * Create a classloader for the specified URLs. + * + * 创建指定 URL 的类加载器 + * + * @param urls the URLs + * @return the classloader + * @throws Exception if the classloader cannot be created + */ +protected ClassLoader createClassLoader(URL[] urls) throws Exception { + // 这里的 LaunchedURLClassLoader 是 SpringBoot loader 给我们提供的一个全新的类加载器 + // 参数 urls 是 class 文件或者资源配置文件的路径地址 + // 参数 getClass().getClassLoader() 是应用类加载器 + return new LaunchedURLClassLoader(urls, getClass().getClassLoader()); +} + + +/** + * Create a new {@link LaunchedURLClassLoader} instance. + * @param urls the URLs from which to load classes and resources + * @param parent the parent class loader for delegation + */ +public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); +} +``` + +super() 方法是调用父类的方法,这样一层层跟进去,最终到了 JDK 的 `ClassLoader` 类,它也是所有类加载器的顶类 + +#### launch 方法 + +{% label success@launch %} 方法的第二个参数,getMainClass() 是一个抽象方法 + +```java +/** + * Returns the main class that should be launched. + * @return the name of the main class + * @throws Exception if the main class cannot be obtained + */ +protected abstract String getMainClass() throws Exception; + +///////////////////////////////////////////////////////////// +// 紧接着我们查看 getMainClass() 实现 // +///////////////////////////////////////////////////////////// + +@Override +protected String getMainClass() throws Exception { + Manifest manifest = this.archive.getManifest(); + String mainClass = null; + if (manifest != null) { + // 获取到 Manifest 文件中属性为`Start-Class`对应的值,也就是当前项目工程启动的类的完整路径 + mainClass = manifest.getMainAttributes().getValue("Start-Class"); + } + if (mainClass == null) { + throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this); + } + return mainClass; +} +``` + +接着我们看 {% label success@launch %} 方法 + +```java +/** + * Launch the application given the archive file and a fully configured classloader. + * + * 加载指定存档文件和完全配置的类加载器的应用程序 + * + * @param args the incoming arguments + * @param mainClass the main class to run + * @param classLoader the classloader + * @throws Exception if the launch fails + */ +protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { + // 将应用的加载器换成了自定义的 LaunchedURLClassLoader 加载器,然后入到线程类加载器中 + // 最终在未来的某个地方,通过线程的上下文中取出类加载进行加载 + Thread.currentThread().setContextClassLoader(classLoader); + // 创建一个主方法运行器运行 + createMainMethodRunner(mainClass, args, classLoader).run(); +} + +/** + * Create the {@code MainMethodRunner} used to launch the application. + * + * 创建一个 MainMethodRunner 用于启动这个应用 + * + * @param mainClass the main class + * @param args the incoming arguments + * @param classLoader the classloader + * @return the main method runner + */ +protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) { + return new MainMethodRunner(mainClass, args); +} +``` + +返回一个 `MainMethodRunner` 对象,我们紧接着去看看这个对象, + +```java +/** + * Utility class that is used by {@link Launcher}s to call a main method. The class + * containing the main method is loaded using the thread context class loader. + * + * 被 Launcher 使用来调用 main 方法的辅助类,使用线程类加载来加载包含 main 方法的类 + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +public class MainMethodRunner { + + private final String mainClassName; + + private final String[] args; + + /** + * Create a new {@link MainMethodRunner} instance. + * @param mainClass the main class + * @param args incoming arguments + */ + public MainMethodRunner(String mainClass, String[] args) { + this.mainClassName = mainClass; + this.args = (args != null) ? args.clone() : null; + } + + // 关键方法 + public void run() throws Exception { + // 获取到当前线程上下文的类加载器,实际就是 springboot 自定义的加载器(LaunchedURLClassLoader) + // 加载 this.mainClassName所对应的类,实际也就是清单文件中对应 Start-Class 属性的类 + Class mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); + // 通过反射获取到 main 方法和参数 + Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); + // 调用目标方法运行 + // invoke 方法参数一:是被调用方法所在对象,这里为 null,原因是我们所调用的目标方法是一个静态方法 + // invoke 方法参数二:被调用方法所接收的参数 + mainMethod.invoke(null, new Object[] { this.args }); + } + +} +``` + +到此为止,invoke 方法成功调用,那么我们项目中的main 方法就执行了,这时我们的所编写的 springboot 应用就正式的启动了。那么关于 springboot 的 loader 加载过程已经分析完 + +## 总结 + +![summary-jarlauncher](https://res.cloudinary.com/incoder/image/upload/v1604881134/blog/summary-jarlauncher.jpg) + +从 jar 规范的角度出发,我们深入分析了 springboot 项目启动的整个过程,这个过程到底对不对,我们口说无凭,需要实际检验我们分析 +首先,我们先思考,项目的应用启动入口是不是必须是 {% label success@main.class %} 方法,以及为什么要默认这么做? +其次,我们再思考,在编辑器中通过图标运行启动程序(或者是通过命令启动程序),比较将程序编译成 jar 包,然后通过命令启动程序他们之间是否相同,如果不同请解释为什么? + +### 问题一 + +项目的应用启动入口可以不是 {% label success@main.class %} 方法,只是为什么会默认为 {% label success@main.class %} 方法,原因是在 springboot 的 MainMethodRunner类的 run 方法中,是固定写死的 {% label success@main %} ,为什么要这么写,答案是,我们可以在编辑器中已右键或其他图标启动的方式快速启动 springboot 项目(就像是在运行一个 Java 的 {% label success@main %} 方法一样,不再向之前需要乱七八糟各种的配置)。 + +### 问题二 + +答案是不相同,我们可以在项目的应用启动 {% label success@main.class %} 方法中,打印出加载类 {% label info@System.out.println("项目启动加载类" + SpringbootStartApplication.class.getClassLoader()); %} ,这样就可以检验我们的分析是否正确。分别使用两种不同的方式 +* 方式一:在编辑器中之间运行(右键,或者控制台输入命令`gradle bootRun`)或者使用 IDEA 上的运行应用运行按钮,结果如下 + ```java + 项目启动加载类sun.misc.Launcher$AppClassLoader@18b4aac2 + ``` +* 方式二:先编译成 jar 包,然后通过 {% label info@java -jar build-name.jar %} 命令运行 + ```java + 项目启动加载类org.springframework.boot.loader.LaunchedURLClassLoader@439f5b3d + ``` + +通过打印出来的信息,可以验证我们的分析,方式一的运行,实际上是应用类加载器启动,而方式二是 {% label danger@spring-boot-load %} 包中自定义的 `LaunchedURLClassLoader` 来启动项目 + +在实际的生产开发中,有时我们的分析需要进行验证(或者找问题),而此时服务又部署在生成环境或者非本机上,通常用的方式是看应用的日志输出,在日志中去定位问题,而有时我们需要断点的方式去找问题,那该如何去操作呢?对于这个问题,在实际开发中是有方法去处理,请看下篇[《SpringBoot(三) JDWP远程调用》](https://incoder.org/2019/07/11/springboot3/) + +## 附录 + +* [spring_boot_cloud(2)Spring_Boot打包文件结构深入分析源码讲解](https://ceaser.wang/2019/06/07/spring_boot_and_cloud/spring_boot_cloud(2)Spring_Boot%E6%89%93%E5%8C%85%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E6%BA%90%E7%A0%81%E8%AE%B2%E8%A7%A3/) +* [校验者•CeaserWang](https://ceaser.wang/) \ No newline at end of file diff --git a/source/_posts/springboot3.md b/source/_posts/springboot3.md new file mode 100644 index 000000000..921c01f80 --- /dev/null +++ b/source/_posts/springboot3.md @@ -0,0 +1,27 @@ +--- +title: SpringBoot(三) JDWP远程调用 +date: 2019-07-11 22:20:11 +categories: SpringBoot +tag: [SpringBoot] +--- + +在 SpringBoot 系列的第二篇文章中,已经详细分析了 SpringBoot 的启动过程,那么这篇文章,我们通过源码调试的方式来验证我们的分析,首先我们在控制台中输入 `java` 命令,可用输出 JDK 给我们提供了一些命令,其中`-agentlib`命令就是本篇文章所介绍,用于我们进行源码调试 + + + +![springboot-java-agentlib](https://res.cloudinary.com/incoder/image/upload/v1562858657/blog/springboot-java-agentlib.png) +我们继续查看`-agentlib`详细的命令说明,输入`java -agentlib:jdwp=help` 查看帮助文档 +![springboot-java-agentlib-help](https://res.cloudinary.com/incoder/image/upload/v1562859131/blog/springboot-java-agentlib-help.png) + +## 远程 + +```java +# 在远程机器上添加代理模式的方式启动 +# 使用 socket 协议来进行远程调试,当服务启动就开始在 6666 端口等待连接 +java -agentlib:jdwp=transport=dt_socket,server=y,address=6666 -jar start-1.0-SNAPSHOT.jar +``` + +## 本机 + +在本机上,我们直接使用 IDEA 编辑器,新建一个 Remote 应用服务,运行,创建步骤如下 9 步骤 +![springboot-java-remote](https://res.cloudinary.com/incoder/image/upload/v1562860397/blog/springboot-java-remote.png) \ No newline at end of file diff --git a/source/_posts/springboot4.md b/source/_posts/springboot4.md new file mode 100644 index 000000000..92a29bd08 --- /dev/null +++ b/source/_posts/springboot4.md @@ -0,0 +1,256 @@ +--- +title: SpringBoot(四)配置文件 +date: 2019-07-28 21:20:01 +categories: SpringBoot +tag: [SpringBoot] +--- + +关于 SpringBoot 配置文件,在之前的文章中已经提到[配置文件格式](https://incoder.org/2019/06/23/springboot1/#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F),主要是两种格式的配置,这里并没有哪个配置写法一定优于另一种写法,对于配置文件名(application.yml 或者 application.properties),可以更改,为了减少不必要的麻烦,不建议修改,本篇文章以 yml 文件作为示例 + + + +本篇文章示例代码见:[springboot-config](https://github.com/RootCluster/rc-cluster-springboot/tree/master/springboot-config) + +## YAML + +[YAML](https://en.wikipedia.org/wiki/YAML)是JSON的超集,因此是用于指定分层配置数据的便捷格式。只要在类路径上有SnakeYAML库,SpringApplication类就会自动支持YAML作为属性的替代 。 + +### 语法规则 + +* 大小写敏感 +* 缩进(只能使用空格,空格数量不重要)表示层级 +* 注释用 `#` 符号 + +### 数据结构 + +1. 不可再分的单个的值,如数字,字符串等。 + ```yml + env: dev + crate-date: 2020 + is-mac: true + + # ~表示NULL值 + email: ~ + + # 多行字符串可以使用 | 保留换行符,也可以使用 > 折叠换行 + # + 表示保留文字块末尾的换行,- 表示删除字符串末尾的换行 + message: |- + hello world ${crate-date} + + # 单引号 + # 会转义特殊字符,特殊字符最终只是一个普通的字符串数据 + # 输出:mac \n catalina + name1: 'mac \n catalina' + + # 双引号 + # 不会转义字符串里面的特殊字符,特殊字符会作为本身想表示的意思 + # 输出:mac + # catalina + name2: "mac \n catalina" + ``` +2. 数组,一组按次序排列的值 + ```yml + # 这种写法,必须有两层结构,而且第二层(language 名字)是必须满足 Java 属性字段命名规则 + list: + language: + - 'object-c' + - 'swift' + - 'c' + # 或者行内写法 + list-program-languages: object-c, swift, c + # SpEL 获取数组 + el: + list: object-c, swift, c + ``` +3. 对象,键值对的集合 + ```yml + # 对象 + object: + name: Jerry + age: 20 + + # 或者行内写法 + persons: { name: Jerry, age: 20 } + + # Map + map-object: + map: + key1: value1 + key2: value2 + + # 或者行内写法 + mapObjects.maps: { key1: value1,key2: value2 } + ``` +4. 随机数 + ```yml + # 随机数 + secret: ${random.value} + number: ${random.int} + bignumber: ${random.long} + uuid: ${random.uuid} + ``` +5. 默认值,占位符获取之前配置的值,如果没有可以是用:指定默认值 + ```yml + bootapp.name: SpringBoot + description: ${bootapp.name}是一个spring应用程序 + ``` + +{% note warning %} +1. `:` 号后面有一个空格 +2. 对于复杂的数据结构(对象,List,Map),需要配套 @ConfigurationProperties 定义对应的对象 +{% endnote %} + +## 配置 + +为了在配置自定义属性时,向配置 SpringBoot 属性自动提示的功能,导入如下的包 +```xml + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-autoconfigure + +``` +或者在 gradle 配置文件中添加依赖 +```gradle +compileOnly 'org.springframework.boot:spring-boot-configuration-processor' +annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" +``` +如果任然无法自动提示,请查看你的编辑器 IDEA 中是否开启了 `Annonation Processing` +![idea-annotation-processors](https://res.cloudinary.com/incoder/image/upload/v1566701581/blog/idea-annotation-processors.png) + +### 定义配置 + +#### 默认配置 + +[Common application properties](https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html),是 SpringBoot 官方提供的一些默认配置属性说明,如果需要更改,只需要在 application.yml 文件中重写该属性即可,比如重新设置服务的启动端口为 9090 +```yml +server: + port: 9090 +``` + +#### 自定义配置 +首先在 application.yml 文件中根据上面所述的规则写法进行自定义字段的声明 +```yml +myConfig: + maps: + key: value +``` + +### 配置的使用 + +| | @Value | @ConfigurationProperties | @PropertySource | +|-------------------------|------------------|--------------------------|-----------------------------| +| 使用场景 | 单一属性注入,注解写在类的属性上 | 批量注入,注解写在类上 | 加载自定义配置文件,用于静态类获取配置文件中定义的信息 | +| 松散语法 | 不支持 | 支持 | 支持 | +| SpEL | 支持 | 不支持 | 支持 | +| JSR-303 数据校验 @Validated | 不支持 | 支持 | 支持 | +| 复杂类型(数组,Map,对象等) | 不完全支持(数组支持行内定义) | 支持 | 支持 | + +#### ConfigurationProperties + +@ConfigurationProperties 注解是 SpringBoot提供的一种使用属性的注入方法,不仅可以方便的把配置文件中属性值与所注解类绑定,还支持松散绑定,JSR-303 数据校验等功能 + +```java +/** + * 使用 @ConfigurationProperties 配置属性必须是小写,多单词之间可用'-'连接 + * + * @author : Jerry xu + * @since : 2020/3/29 16:26 + */ +@Data +@Component +@ConfigurationProperties(prefix = "list") +public class ConfigListBean { + + /** + * 这里不能再用 @Value("") 去加载,修饰当前的字段, + * 必须指定其属性名和配置文件中定义的名称一致 + */ + private List language; + +} +``` + +#### Value + +@Value 注解支持直接从配置文件中读取值,同时支持 [SpEL](https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/expressions.html) 表达式,但是不支持复杂数据类型和数据验证 + +使用 @Value 获取配置文件中定义的值,通常其类是被 @Controller@Service@Component 等注解修饰,如果是一般普通的类(如一些工具类)并 **不能** 只接获取到配置文件中定义的值 + +```java +@Data +@Component +public class GradleDataBean { + + @Value("${env}") + private String env; + @Value("${crate-date}") + private Integer createDate; + @Value("${is-mac}") + private Boolean isMac; + @Value("${email}") + private String email; + @Value("${message}") + private String message; + @Value("${name1}") + private String name1; + @Value("${name2}") + private String name2; + + @Value("${list-program-languages}") + private List programLanguages; + + /** + * 使用el表达式,获取定义数组 + */ + @Value("#{'${el.list}'.split(',')}") + private List programList; + + @Value("${secret}") + private String secret; + @Value("${number}") + private Integer number; + @Value("${bignumber}") + private Long bigNumber; + @Value("${uuid}") + private String uuid; +} +``` + +#### PropertySource + +@PropertySource 注解加载自定义配置文件,由于 @PropertySource 指定的文件会优先加载,所以如果在 applocation.properties 文件中存在相同的属性配置,会覆盖前者中对应的值,且 @PropertySource 不支持 yml 文件注入 + +### 多环境配置 + +在实际的开发中,不同环境对应不同的配置,因此我们需要根据环境来配置不同的项目配置信息,SpringBoot 也是支持我们进行多环境的配置,通常情况下,我们命名为 `application-{profile}.properties/yml` ,其中 +`{profile}`表示不同的环境,比如:dev(开发),prod(线上) 等 + +关于具体的多环境配置,可以参考文章 [SpringBoot(五)多环境配置](https://incoder.org/2020/02/02/springboot5/) + +## 配置文件加载顺序 + +Spring Boot 启动会扫描以下位置的配置文件(application.properties 或 application.yml) 作为Spring Boot 的默认配置文件 + +1. -file:./config/ +2. -file:./ +3. -classpath:/config/ +4. -classpath:/ + +加载顺序可以查看下图 +![application-sort](https://res.cloudinary.com/incoder/image/upload/v1585239269/blog/application-sort.png) + +优先级从高到低,高优先级的配置会覆盖低优先级的配置 + +## 参考 + +* [介绍两种SpringBoot读取yml文件中配置数组的方法](https://blog.csdn.net/liujianyangbj/article/details/108810726) +* [Spring的@Value可以注入复杂类型吗?今天教你通过@value注入自定义类型](https://blog.csdn.net/liujianyangbj/article/details/111352703) +* [Properties and Configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html) \ No newline at end of file diff --git a/source/_posts/springboot5.md b/source/_posts/springboot5.md new file mode 100644 index 000000000..ebaf36eb2 --- /dev/null +++ b/source/_posts/springboot5.md @@ -0,0 +1,102 @@ +--- +title: SpringBoot(五)多环境配置 +date: 2020-02-02 15:43:00 +categories: SpringBoot +tag: [SpringBoot] +--- + +在 SpringBoot 项目中,常用的包管理分别为 maven 和 gradle,在不同包管理下我们如何实现多环境的项目配置,这是实际项目开发过程汇总必备的一项技能,可以大大提高我们开发部署效率,同时也避免了人为的频繁改动配置造成的问题等,有些人可能会问了,maven 不是用的好好的嘛,干嘛还要用 gradle,首先我们可以看现在主流开源项目在提供引入方式时都是有提供了 gradle 依赖方式,以及 gradle 支持编写脚本,在很大程度上让管理更加便捷和人性化 + + + +废话不多说,我们直接来看代码吧,首先我们先来看看使用 maven 来进行多环境的配置 + +## maven + +### pom.xml 文件 + +```xml + + + + + dev + + dev + + + true + + + + + prod + + prod + + + false + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + src/main/resources + true + + application*.yml + + + + src/main/resources + true + + application.yml + application-${profileActive}.yml + + + + +``` + +### application*.yml 文件 + +根据上面我们的配置,我们首先排除 resources 目录下的所有 `application*.yml` 文件,然后再导入 `application.yml`,`application-${profileActive}.yml` 文件,那么通常在 `application.yml` 文件中我们放置一些与环境无关的配置,在 `application-${profileActive}.yml` 文件中根据不同环境配置不同的属性(比如:数据库连接,日志等级等配置) + +根据 SpringBoot 加载资源文件的顺序,如果我们在 `application-${profileActive}.yml` 文件中配置了与 `application*.yml` 文件相同的属性,那么 `application-${profileActive}.yml` 会覆盖掉 `application*.yml` 文件中相同属性配置 + +#### application.yml + +```yml +# 动态激活运行的环境,默认是 dev +# 当然你也可以在你的 pom.xml 文件中进行默认激活的环境更改 +spring: + profiles: + active: @profileActive@ +``` + +## gradle + + +### 编译打包 + +## 部署 + +关于应用的部署,可以参考[IDEA 之 SpringBoot 应用部署](https://incoder.org/2019/11/19/deploy-springboot/),这里不再过多进行说明 + +## 附录 + +* [SpringBoot 2.1.6.RELEASE 官方指南](https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/) +* [SpringBoot 中文指南](https://www.springcloud.cc/spring-boot.html) +* [Gradle Builds From Apache Maven](https://docs.gradle.org/current/userguide/migrating_from_maven.html#migmvn:profiles_and_properties) +* [灵活强大的构建系统 Gradle](https://tech.meituan.com/2014/08/18/gradle-practice.html) \ No newline at end of file diff --git a/source/_posts/springboot6.md b/source/_posts/springboot6.md new file mode 100644 index 000000000..c18f4fac3 --- /dev/null +++ b/source/_posts/springboot6.md @@ -0,0 +1,14 @@ +--- +title: SpringBoot(六)日志管理 +date: 2020-02-22 09:03:00 +categories: SpringBoot +tag: [SpringBoot] +--- + +日志是我们项目开发过程中必不可少的一个方面, 当我们项目引入了 `spring-boot-starter-web` 这个 jar 包,会自动引入相关的一些日志相关的 jar 包,比如,其实在项目中可供我们选择的 jar 包有很多,比如 `log4j`,`log4j2`,`logback`(现在使用最多),`slfj`等,在具体使用时并不会直接去使用`log4j`,`log4j2`而是使用`slfj`作为门面,具体的实现是通过可插拔的方式提供。`logback`实际是在`log4j`之后作者重新写的一个日志框架。本篇文章主要讲`logback`在项目中的应用 + + + +## logback-spring.xml + +文件名使用 `logback-spring.xml`,位于 resource 路径下 \ No newline at end of file diff --git a/source/_posts/springboot7.md b/source/_posts/springboot7.md new file mode 100644 index 000000000..8324a9664 --- /dev/null +++ b/source/_posts/springboot7.md @@ -0,0 +1,313 @@ +--- +title: SpringBoot(七)注解 +date: 2020-02-25 10:01:00 +categories: SpringBoot +tag: [SpringBoot] +--- + +SpringBoot 与 SpringCloud 微服务技术栈体系本质就是围绕注解来展开,这些注解在微服务框架中扮演非常重要的角色,每个注解都有他的应用场景,通过一些注解的组合让 SpringBoot 与 SpringCloud 开发变的简单和高效,本篇文章我们就来汇总 SpringBoot 相关的注解 + +本篇文章基于如下版本 +* Spring:5.1.8 RELEASE +* SpringBoot:2.1.6 RELEASE + + + +>由于本篇包含众多注解,请配合 `Ctrl` + `F` (或 `⌘` + `F`)使用 +>绿色 :**基础** 的注解 +>红色 :**常用** 的注解 + +由于 SpringBoot 的基础是 Spring,SpringBoot 相关部分注解都是在 Spring 的基础注解上的再组合,因此我们先来学习 Spring 的相关基础注解,在注解中大部分注解的修饰中包含已下几个注解,这里统一来说明下 + +## Java 相关 + +### @Documented + +在默认情况下Documented注解表明这个注释是由 javadoc 记录的也有类似的记录工具。 如果一个类型声明被注释了文档化,它的注释成为公共API的一部分 +* 路径:java.lang.annotation +* 引入:JDK 1.5 开始引入 + +### @Inherited + +说明子类可以继承父类中的该注解,注释类型是自动继承的。 +* 如果注释类型声明中存在继承的元注释,并且用户在类声明中查询该注释类型,并且该类声明中没有该类型的注释,则将自动查询该类的超类以获取注释类型。重复此过程,直到找到该类型的注释,或到达类层次结构(对象)的顶部为止。 +* 如果没有超类对此类型进行注释,则查询将指示所讨论的类没有此类注释。 + +>请注意,如果带注释的类型用于 **注释除类之外** 的任何内容,则此元注释类型无效。还要注意,此元注释仅使注释从超类继承;已实现的接口上的注释无效 + +* 路径:java.lang.annotation +* 引入:JDK 1.5 开始引入 + +### @Retention + +注解的保留位置,RetentionPolicy 是提供的策略枚举 +1. SOURCE:注解将被编译器丢弃,比如:@Override,@SupressWarnings +2. CLASS:注释将由编译器记录在类文件中,但不必在运行时由VM保留。这是默认的行为 +3. RUNTIME:注释由编译器记录在类文件中,并由在运行时由VM保留,因此可以通过反射方式读取它们,比如 @Deprecated + +>@Retention(RetentionPolicy.RUNTIME) // 作用于运行期 + +* 路径:java.lang.annotation +* 引入:JDK 1.5 开始引入 + +### @Target + +用于描述注解的使用范围(即:被描述的注解可以用在什么地方),ElementType 是提供的策略枚举 +1. ANNOTATION_TYPE:用于注释类型 +2. CONSTRUCTOR:用于描述构造器 +3. FIELD:用于描述字段(包括枚举常量) +4. LOCAL_VARIABLE:用于描述局部变量 +5. METHOD:用于描述方法 +6. PACKAGE:用于描述包 +7. PARAMETER:用于描述参数 +8. TYPE:用于描述类、接口(包括注解类型) 或enum声明 +9. TYPE_PARAMETER:用于参数类型 +10. TYPE_USE:用于使用类型 + +>@Target(ElementType.ANNOTATION_TYPE) // 作用于注释类型 + +* 路径:java.lang.annotation +* 引入:JDK 1.5 开始引入 + +## Spring 相关 + +### context.annotation + +路径:org.springframework.context.annotation + +#### @Bean + +* 引入:Spring 3.0 开始引入 + +#### @ComponentScan + +一个组合注解,默认会装配标识了@Controller,@Service,@Repository,@Component 注解到 Spring 容器中 + +* 引入:Spring 3.1 开始引入 + +>@ComponentScan 与 @ComponentScans + +#### @Conditional + +可以根据代码中设置的条件装载不同的 bean,在设置条件注解之前,先要把装载的 bean 类去实现 Condition 接口,然后对该实现接口的类设置是否装载的条件。 + +SpringBoot 注解中的 @ConditionalOnProperty,@ConditionalOnBean 等以 @Conditional* 开头的注解,都是通过集成了@Conditional 来实现相应功能 + +* 引入:Spring 4.0 开始引入 + +#### @Configuration + +用于自定义配置类,可替换 XML 配置文件,被注解的类内部包含有一个或多个被 @Bean 注解的方法,这些方法会将被 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描,并用于构建 bean 定义,初始化 Spring 容器 + +* 引入:Spring 3.0 开始引入 + +#### @DependsOn +#### @Description +#### @EnableAspectJAutoProxy +#### @EnableLoadTimeWeaving +#### @EnableMBeanExport + +#### @Import + +通过导入的方式实现把实例加入 SpringIOC 容器中。可以在需要时将没有被 Spring 容器管理的类导入至 Spring 容器中 + +* 引入:Spring 3.0 开始引入 + + +#### @ImportResource + +和 @Import 类似,区别就是 @ImportResource 导入的是配置文件 + +* 引入:Spring 3.0 开始引入 + +#### @Lazy + +#### @Primary + +#### @Profile + +#### @PropertySource + +>@PropertySource 与 @PropertySources + +#### @Role + +#### @Scope + +### stereotype + +路径:org.springframework.stereotype + +#### @Component + +是一个元注解,意思是可以注解其他类注解,比如:@Controller @Service @Repository 带此注解的类被看做组件,当使用基于注解的配置和类路径扫描的时候,这些类就会被实例化。其他类级别的注解也可以被认定为是一种特殊类型的组件,比如@Controller(注入服务),@Service(注入 DAO),@Repository(实现 DAO 访问)。 + +@Component 泛指组件,当组件不好归类时,我们可以使用这个注解进行标注,作用相当于 XML 配置,`` + +* 引入:Spring 2.5 开始引入 + +#### @Controller + +#### @Indexed + +#### @Repository + +#### @Service + + + +## SpringBoot 相关 + +### spring-boot +spring-boot:2.1.6.RELEASE + +#### @SpringBootConfiguration +路径:org.springframework.boot + +#### context.properties + +路径:org.springframework.boot.context.properties + +##### @ConfigurationProperties +##### @ConfigurationPropertiesBinding +##### @DeprecatedConfigurationProperty +##### @EnableConfigurationProperties +##### @NestedConfigurationProperty + +#### convert +路径:org.springframework.boot.convert +##### @DataSizeUnit +##### @Delimiter +##### @DurationFormat +##### @DurationUnit + +#### jackson +路径:org.springframework.boot.jackson +##### @JsonComponent + +#### web.server +路径:org.springframework.boot.web.server +##### @LocalServerPort + +#### web.servlet +路径:org.springframework.boot.web.servlet +##### @ServletComponentScan + +### spring-boot-autoconfigure + +spring-boot-autoconfigure:2.1.6.RELEASE + +org.springframework.boot.autoconfigure +#### @AutoConfigurationPackage +#### @AutoConfigureAfter +#### @AutoConfigureBefore +#### @AutoConfigureOrder +#### @EnableAutoConfiguration +#### @ImportAutoConfiguration + +#### @SpringBootApplication + +表示一个配置类,它声明一个或多个 Bean 方法并且会触发自动配置以及组件扫描,这是一个很便捷的注解,@SpringBootApplication 相当于同时使用 @Configuration、 @EnableAutoConfiguration、 @ComponentScan这三个注解 + +```java +@Target(ElementType.TYPE) // 当前注解所修饰的对象范围:用于描述类,接口,enum声明 +@Retention(RetentionPolicy.RUNTIME) // 作用于运行期 +@Documented // 生成文档 +@Inherited // 继承 +@SpringBootConfiguration +@EnableAutoConfiguration +@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), + @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) +public @interface SpringBootApplication { + +} +``` + +* 引入:SpringBoot 1.2.0开始引入 + +#### @SpringBootConfiguration + +#### condition +路径:org.springframework.boot.autoconfigure.condition +##### @ConditionalOnBean +##### @ConditionalOnClass +##### @ConditionalOnCloudPlatform +##### @ConditionalOnExpression +##### @ConditionalOnJava +##### @ConditionalOnJndi +##### @ConditionalOnMissingBean +##### @ConditionalOnMissingClass +##### @ConditionalOnNotWebApplication +##### @ConditionalOnProperty +##### @ConditionalOnResource +##### @ConditionalOnSingleCandidate +##### @ConditionalOnWebApplication + +#### data +路径:org.springframework.boot.autoconfigure.data +##### @ConditionalOnRepositoryType + +#### domain +路径:org.springframework.boot.autoconfigure.domain +##### @EntityScan + +#### flyway +路径:org.springframework.boot.autoconfigure.flyway +##### @FlywayDataSource + +#### liquibase +路径:org.springframework.boot.autoconfigure.liquibase +##### @LiquibaseDataSource + +#### quartz +路径:org.springframework.boot.autoconfigure.quartz +##### @QuartzDataSource + +## SpringCloud 相关 + +## JPA 注解 + +### @Column +### @Entity +### @GeneratedValue +### @Id +### @JoinColumn +### @JsonIgnore +### @MappedSuperClass +### @NoRepositoryBean +### @OneToOne、@OneToMany、@ManyToOne +### @SequenceGeneretor +### @Transient + +## 异常 + +### @ControllerAdvice + +### @ExceptionHandler + + +## 其他注解 + +### @Autowired +### @Inject +### @JsonBackReference +### @PathVariable +### @Qualifier +### @RepositoryRestResourcepublic +### @RequestMapping +### @Resource +### @ResponseBody +### @RestController +### @Value + +## 附录 + +* [Spring Boot 注解如何系统的学习](https://www.zhihu.com/question/324251802) +* [SpringBoot 系列(三)Spring Boot 自动配置](https://www.codingme.net/2019/01/springboot/springboot03-auto-config/) +* [Spring boot 2.x注解Annotation大全](https://juejin.im/post/5d1a1907e51d45572c06009c#heading-18) \ No newline at end of file diff --git a/source/_posts/springboot8.md b/source/_posts/springboot8.md new file mode 100644 index 000000000..d62358317 --- /dev/null +++ b/source/_posts/springboot8.md @@ -0,0 +1,30 @@ +--- +title: SpringBoot(八)SpringApplication 源码分析 +date: 2020-02-26 09:10:00 +categories: SpringBoot +tag: [SpringBoot] +--- + +正如我们看到的 SpringBoot 应用启动入口类,main() 方法中一行简单 `SpringApplication.run(MyApplication.class, args);` 就可以将 SpringBoot 应用给启动了。那么它肯定是在`SpringApplication`中做了大量的工作,才能将应用启动,因此本篇文章我们来一起看看这个核心的类 + +SpringApplication 类可以从 Java 的 main 方法中引导和启动 Spring 的应用,默认情况下它会按照如下的启动步骤 +1. 创建一个恰当的 ApplicationContext 实例(取决于你的 classpath 路径) +2. 注册一个 CommandLinePropertySource 将命令行参数作为 Spring 的属性(换句话说,可以通过命令行来传递当前应用所需要的一些属性) +3. 刷新应用的 Context(内容上下文),并加载所有单例的 beans +4. 触发每个 CommandLineRunner beans + + + +大多数情况下,我们在 main() 方法中直接使用静态的 run(Class, String[]) 方法启动加载应用,对于一些高级的配置,我们可以创建一个 `SpringApplication` 实例,在运行之前进行自定义的配置 +```java +public static void main(String[] args) { + SpringApplication application = new SpringApplication(MyApplication.class); + // ... customize application settings here + application.run(args) +} +``` + +`SpringApplication` 可以读取不同来源的 bean 信息,通常推荐我们使用被 `@Configuration` 修饰的单例类来启动应用,然而你也可以在多种来源去设置你的 source +* 使用`AnnotatedBeanDefinitionReader`加载完全限定类名 +* 使用`XmlBeanDefinitionReader`读取本地 XML 资源,或者使用`GroovyBeanDefinitionReader` 读取 Groovy 脚本去加载 +* 使用`ClassPathBeanDefinitionScanner`扫描得到包的名字 \ No newline at end of file diff --git a/source/_posts/springboot9.md b/source/_posts/springboot9.md new file mode 100644 index 000000000..b1ba89b46 --- /dev/null +++ b/source/_posts/springboot9.md @@ -0,0 +1,26 @@ +--- +title: SpringBoot(九)普通类如何获取配置文件中的值 +date: 2020-06-26 07:10:00 +categories: SpringBoot +tag: [SpringBoot] +--- + +在之前的 SpringBoot 学习中,我们知道,可以在项目的 `application.yml` 文件或 `application.properties` 文件中获取到一些我们项目中的一些静态值。但对于一些非 Spring 所管理的类(比如一些工具类)该如何获取到定义在配置文件中的值呢?而这些工具类中的配置值和对应项目运行的环境有关,我们如果固定写在代码内,每次打包时去更改,显然这种做法不够好,那么本篇文章就来实践一些非 Spring 类如何获取配置的值 + + + +在[《SpringBoot(四)配置文件》](https://incoder.org/2019/07/28/springboot4) 文章中提到,在使用 `@Value` 注解时,当时提到说 “使用 `@Value` 获取配置文件中定义的值,通常其类是被 `@Controller`,`@Service` ,`@Component` 等注解修饰,如果是一般普通的类(如一些工具类)并 **不能** 只接获取到配置文件中定义的值”,显然我们直接按照之前的套路,在`application.yml`等配置文件中添加自定义的配置,使用 `@Value` 注解在我们的普通类中是无法获取到的,其实要解决这个问题很简单,那就是将当前的普通类变成一个被 Spring 所接管的类(普通类上使用 `@Component` 来修饰),这样我们就可以使用了 + +就这?你以为就结束了,那我还写个啥笔记,你在操作完发现你依然拿不到在配置文件中定义的静态值,接下来让我一步步来告诉你怎么来写,和为啥会这么做的原因 + + +### 配置文件 + +## 参考 + +1. [工具类用单例模式还是静态方法](https://blog.csdn.net/huantai3334/article/details/104504208) +2. [springboot 工具类加载配置对象](https://blog.csdn.net/sunnyzyq/article/details/102457434) +3. [静态方法(工具类)中调用Spring管理的Bean](https://blog.csdn.net/xiangwang2016/article/details/106148880) +4. [spring实现静态注入](https://segmentfault.com/a/1190000019844427) +5. [工具类该用单例模式,还是用静态的方式](https://blog.csdn.net/qq_35584878/article/details/91438648) +6. [单例模式工具类中Spring 注入为空 的一些小问题](https://blog.csdn.net/jzy2046/article/details/89948124) \ No newline at end of file diff --git a/source/_posts/springcloud1.md b/source/_posts/springcloud1.md new file mode 100644 index 000000000..87b6ae587 --- /dev/null +++ b/source/_posts/springcloud1.md @@ -0,0 +1,181 @@ +--- +title: SpringCloud(一)Security OAuth2 +date: 2020-07-11 07:10:00 +categories: SpringCloud +tag: [SpringCloud, OAuth2] +--- + +## 什么是 OAuth2 + +用于 REST/APIs 的代理授权框架(**delegated authorization** framework),基于令牌 Token 的授权,在无需暴露用户密码的情况下,使应用能获取对用户数据的有限访问权限,做到解耦认证和授权 + +OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如:头像,照片,视频等),而在这个过程中无线将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据 + + + +## 什么是 Spring Security + +Spring Security 是为基于 Spring 的应用程序提供声明书安全保护的安全性框架。Spring Security 提供了完整的安全性解决方案,它能够在 Web 请求级别和方法调用级别处理身份认证和授权。因为基于 Spring 框架,所以 Spring Security 充分利用了依赖注入(Dependency Injection,DI)和面向切面的技术 + +最初,Spring Security 被称为 Acegi Security。Acegi 是一个强大的安全框架,但是它存在一个严重的问题,那就是需要大量的 XML 配置。到了 2.0 版本,Acegi Security 更名为 Spring Security,2.0版本所带来的不仅仅是名字的变化。为了在 Spring 中配置安全性,Spring Security 引入一个全新的、与安全性相关的 XML 命名空间。这个新的命名空间联通注解和一些合理的默认设置,将典型的安全性配置从几百行 XML 减少到十几行。Spring Security 3.0 融入了 SpEL,这将进一步简化 路安全性配置 + +Spring Security 从两个角度来解决安全问题。 +* 它使用 Servlet 规范中的 Filter 保护 Web 请求并限制 URL 级别的访问 +* 它还能够使用 Spring AOP 保护方法调用——借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法 + +## Spring Security OAuth 现状 + +[Spring Security OAuth](https://spring.io/projects/spring-security-oauth) 的模块已被废弃,后续功能已经迁移到 Spring Security 5.2.x 中,但不会再提供 Authorization Server 的功能。 + +为此,随着 Spring Security5.2 发布,Spring 官方强烈鼓励用户开始将其旧版 OAuth2 客户端和资源服务器应用迁移到 Spring Security5.2 中的新支持 + +[具体的功能列表](https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix)请移步查看 + +## OAuth2 主要角色 + +1. 客户应用(Client Application):通常是一个 Web 或无线应用,它需要访问用户的受保护资源 +2. 资源服务器(Resource Server):是一个 Web 站点或者 Web service API,用户的受保护数据保存于此 +3. 授权服务器(Authorized Server):在客户应用成功认证并获得授权之后,向客户应用颁发访问令牌 Access Token +4. 资源拥有者(Resource Owner):资源的拥有人,想要分享某些资源给第三方应用 +5. 客户凭证(Client Credentials):客户的 clientId 和密码用于认证客户 +6. 令牌(Tokens):授权服务器在接收到客户请求后,颁发的访问令牌 + * 授权码(Authorization Code Token):仅用于授权码授权类型,用于交换获取访问令牌和刷新令牌 + * 刷新令牌(Refresh Token):用于去授权服务器获取一个新的访问令牌 + * **访问令牌(Access Token)**:用于代表一个用户或服务直接去访问受保护的资源 + * Bearer Token:不管谁拿到 Token 都可以访问资源,像现钞 + * Proof of Possession(PoP) Token:可以校验 client 是否对 Token 有明确的拥有权 +7. 作用域(Scopes):客户请求访问令牌时,由资源拥有者额外指定的细分权限 + +## OAuth2 误解 + +* OAuth 并没有支持 **HTTP 以外的协议** +* OAuth 并不是一个 **认证协议** +* OAuth 并没有定义 **授权处理机制** +* OAuth 并没有定义 **Token 格式** +* OAuth 2.0 并没有定义 **加密方法** +* OAuth 2.0 并不是 **单个** 协议 +* OAuth 2.0 仅是 **授权框架**,仅用于授权代理 + +### OAuth2 运行流程 + +![来自 RFC 6749](https://res.cloudinary.com/incoder/image/upload/v1594815003/blog/OAuth2.png) + +从上面的流转过程,经过下面六个步骤,客户端就能获取到访问资源的令牌,其中第二步是关键,用户要怎样才能给予客户端授权(客户端获取授权的四种模式) + +1. (A)用户打开客户端后,客户端要求用户给予权限 +2. (B)用户同意给予客户端权限 +3. (C)客户端使用上一步获取的授权,向认证服务器申请令牌 +4. (D)认证服务器对客户端进行认证后,确认无误,同意发放令牌 +5. (E)客户端使用令牌,向资源服务器申请获取资源 +6. (F)资源服务器确认令牌无误,同意向客户端开放资源 + +### Access Token + +Access Token,顾名思义,就是用来访问受保护资源要用到的令牌。客户端要访问资源服务器上受保护的资源,就必须要有 Access Token 作为通行证。Access Token 由授权服务器生成。客户端再获取了用户授权后才能想授权服务器申请 Access Token + +An access token is a string representing an authorization issued to the client. … Tokens represent specific scopes and durations of access, granted by the resource owner, and enforced by the resource server and authorization server. + +从官方的定义来看(RFC 6749 #section-1.4),Access Token 是一个字符串,至少要提供关于 **客户端的基本信息(通常是客户端的 ID)** 和该客户端获得的权限,权限有一组 scopes 表示,并且 Access Token 是有有效期的 + +关于 Access Token 的具体格式一个字符串标识符呢,还是自包含内容的信息呢,在 OAth2(RFC6749) 中并没有规定 + +### Refresh Token + +Refresh Token 也是有授权服务器生成,当一个 Access Token 过期或者失效时,客户端可以使用 Refresh Token 来获取一个新的 Access Token,这个新的 Access Token 拥有 scopes 范围小于等原来的那个 Access Token 权限 + +Refresh Token 是可选项,如果授权服务器生成了 Refresh Token,它会与 Access Token 一起返回给客户端,我们一起来看一看整个流程 + +![refresh-token](https://res.cloudinary.com/incoder/image/upload/v1594806547/blog/refresh-token.png) + +1. (A)客户端通过向服务器镜像身份验证来请求访问令牌,授权服务器并显示授权 +2. (B)授权服务器对客户端进行身份验证并验证权限授予,如果有效,则颁发访问令牌和刷新令牌 +3. (C)客户端携带令牌向资源服务器发出受保护的资源请求 +4. (D)资源服务器验证访问令牌,如果有效,返回受保护的资源给客户端 +5. (E)重复步骤(C)和(D),直到访问令牌过期,如果客户知道访问令牌已过期,则跳至步骤(G);否则,它将发出另一个受保护的资源请求 +6. (F)由于访问令牌无效,因此资源服务器返回无效的令牌错误 +7. (G)客户端通过与进行身份验证来请求新的访问令牌,授权服务器并显示刷新令牌,客户端身份验证要求基于客户端类型和授权策略 +8. (H)授权服务器对客户端进行身份验证并验证刷新令牌,如果有效,则发出新的访问令牌(并且,新的刷新令牌是可选) + +## OAuth2 授权模式 + +关于 OAuth 的授权方式,可以在`spring-security-oauth2-autoconfigure-2.1.2.RELEASE.jar` jar 文件中 +* 类:`org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration` +* 方法:`oauth2ClientDetails()` +* 参数设置:`.setAuthrizedGrantTypes()` 中 list 包含 + * `authorization_code` + * `password` + * `client_credentials` + * `implicit` + * `refresh_token` + +>由于标准的 OAuth2 协议中,授权模式并 **不包括** `refresh_token`,但在 Spring Security 的实现中将其归为一种,因此如果需要实现 `access_token`的刷新,就需要这样一种授权模式 + +### 授权码模式 + +授权码(authorization_code)模式是功能最完整,流程最严谨的授权模式。它的特点就是通过客户端的服务器与授权服务器进行交互,国内常见的第三方平台登录功能基本都是使用这种模式 + +![授权码模式](https://res.cloudinary.com/incoder/image/upload/v1594806818/blog/auth-code.png) + +1. (A)用户访问客户端,后者将前者导向到认证服务器 +2. (B)用户选择是否给予客户端授权 +3. (C)假设用户给予授权,认证服务器将用户导向客户端事先指定的“重定向 URI”,同时附上一个授权码 +4. (D)客户端收到授权码,附上早先的“重定向 URI”,向认证服务器申请令牌,这一步是在客户端的后台的服务器上完成,对用户不可见 +5. (E)认证服务器核对了授权码和重定向 URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token) + +### 密码模式 + +密码(password)模式是用户把账号和密码直接告诉客户端,客户端使用这些信息向授权服务器申请令牌。这需要用户对客户端高度信任,例如客户端和服务提供商是同一家公司 + +![密码模式](https://res.cloudinary.com/incoder/image/upload/v1594806820/blog/password.png) + +1. (A)用户向客户端提供用户名和密码 +2. (B)客户端将用户名和密码发给认证服务器,向后者请求令牌 +3. (C)认证服务器确认无误后,向客户端提供访问令牌 + +### 客户端模式 + +客户端(client_credentials)模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请权限。严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用的这种模式还是非常方便 + +![客户端模式](https://res.cloudinary.com/incoder/image/upload/v1594806819/blog/client.png) + +1. (A)客户端向认证服务器进行身份认证,并要求一个访问令牌 +2. (B)认证服务器确认无误后,向客户端提供访问令牌 + +### 简化模式 + +简化(implicit)模式不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌,一般若网站是纯静态页面,则可以采用这种方式 + +![简化模式](https://res.cloudinary.com/incoder/image/upload/v1594806820/blog/implicit.png) + +1. (A)客户端将用户导向认证服务器 +2. (B)用户决定是否给予客户端授权 +3. (C)假设用户给予授权,认证服务器将用户导向客户端指定的“重定向 URI”,并在 URI 的 Hash 部分包含了访问令牌 +4. (D)浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值 +5. (E)资源服务器返回一个网页,其中包含的代码可以获取 Hash 值中的令牌 +6. (F)浏览器执行上一步获得的脚步,提取出令牌 +7. (G)浏览器将令牌发给客户端 + +## OAuth2/OIDC 相关开源项目 + +* [Redhat Keycloak(Java)](http://www.keycloak.org) +* [Apereo CAS(Java)](https://www.apereo.org/project/cas) +* [IdentityServer(C#)](https://identityserver.io) +* [OpenId-Connect-Java-Spring-Server](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server) +* [OAuth2全家桶项目](https://github.com/newnil/oauth2-family-barrel) +* [OAuth2全家桶项目](https://github.com/monkeyk/oauth2-shiro) +* [Apache Oltu + Shiro 实现 OAuth2 服务器](https://github.com/monkeyk/oauth2-shiro) +* [Using JWT](https://www.baeldung.com/spring-security-oauth-jwt) with [Spring Security OAuth](https://github.com/Baeldung/spring-security-oauth) + +## OAuth2 相关书籍 + +* [OAuth2 in Action](https://www.manning.com/books/oauth-2-in-action):主要讲述 OAuth2 协议的原理知识 +* [OAuth 2.0 Cookbook](https://www.packtpub.com/virtualization-and-cloud/oauth-20-cookbook):主要讲述 OAuth2 相关实践 +* [Developer Guide](https://projects.spring.io/spring-sercurity-oauth/docs/oauth2.html) + +## 附录 + +* [OAuth 2.0 最简向导](https://medium.com/@darutk/the-simplest-guide-to-oauth-2-0-8c71bd9a15bb) 文章【需翻墙】 +* [传统 Web 应用中的身份验证技术](https://insights.thoughtworks.cn/traditional-web-app-authentication/) +* [Spring Security OAuth 2.0 Roadmap Update](https://spring.io/blog/2019/11/14/spring-security-oauth-2-0-roadmap-update) +* [Okta Developer Platform](https://developer.okta.com/) +* [RFC6749 - The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749) \ No newline at end of file diff --git a/source/_posts/springcloud2.md b/source/_posts/springcloud2.md new file mode 100644 index 000000000..a2d75bbe2 --- /dev/null +++ b/source/_posts/springcloud2.md @@ -0,0 +1,599 @@ +--- +title: SpringCloud(二)Security OAuth2 的 四种授权模式 +date: 2020-07-12 09:00:00 +categories: SpringCloud +tag: [SpringCloud, OAuth2] +--- + +在上一篇文章中,我们了解了 Security OAuth2 相关的一些基础知识,和整个四种授权模式的交互过程,那么本篇是对四种模式的实践,废话不多说,我们直接开始,SpringCloud 相关的实践代码均托管在[rc-cluster-springcloud](https://github.com/RootCluster/rc-cluster-springcloud)项目的中,项目使用的一些依赖版本如下 + + + +* gradle:6.1.1 +* SpringBoot:2.2.6.RELEASE +* SpringCloud:Hoxton.SR4 +* JDK:1.8 + +## 实践 + +在实践阶段为了方便,我将资源服务器和授权服务器整合在一个服务上,在后续扩展部分,会提供实际生产环境中的常用做法,先看项目的包依赖 + +```groovy +implementation 'org.springframework.boot:spring-boot-starter-web' +implementation 'org.springframework.cloud:spring-cloud-starter-oauth2' +``` + +下面的两点,不管是什么模式的授权方式,写法都是一样 +1. 业务 API + ```java + /** + * 业务 API + * 为了方便我直接将 UserInfo 对象放在了 Controller 类中 + * + */ + @Controller + public class UserController { + + @GetMapping("/api/userinfo") + public ResponseEntity getUserInfo() { + User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + String email = user.getUsername() + "@gmail.com"; + UserInfo userInfo = new UserInfo(); + userInfo.setName(user.getUsername()); + userInfo.setEmail(email); + // TODO 不同的授权选择不同的模式 + // 授权码模式 + userInfo.setGrantType("authorization_code"); + // 客户端模式 + userInfo.setGrantType("client_credentials"); + // 密码模式 + userInfo.setGrantType("password"); + // 简化模式 + userInfo.setGrantType("implicit"); + return ResponseEntity.ok(userInfo); + } + + public static class UserInfo { + + private String name; + private String email; + private String grantType; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getGrantType() { + return grantType; + } + + public void setGrantType(String grantType) { + this.grantType = grantType; + } + } + } + ``` +2. 资源服务 + ```java + /** + * 资源服务器 + * + * @author : Jerry xu + * @date : 2020/7/17 23:45 + */ + @Configuration + @EnableResourceServer + public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter { + + /** + * 用来配置对资源的访问控制规则 + * 默认设置下,所有非 /oauth/** 路经下的资源都是被保护的 + * + * @param http http + * @throws Exception exception + */ + @Override + public void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .anyRequest() + .authenticated() + .and() + .requestMatchers() + // 对 /api/** 路经下的资源进行了保护 + .antMatchers("/api/**"); + } + } + ``` + +### 权码模式 + +#### 代码实现 + +##### 授权服务器配置 + +```java +@Configuration +@EnableAuthorizationServer +public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter { + + @Resource + private BCryptPasswordEncoder passwordEncoder; + + /** + * 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、 + * 默认不支持 Resource Owner Password 授权类型, + * 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证 + * 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法 + *
    + *
  1. authorization_code:授权码模式
  2. + *
  3. password:密码模式
  4. + *
  5. client_credentials:客户端模式
  6. + *
  7. implicit:简化模式
  8. + *
  9. refresh_token:刷新 token 模式
  10. + *
+ * + * @param clients clients + * @throws Exception exception + */ + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // 在内存中,用于演示,不适用于实际生产环境 + clients.inMemory() + // withClient + secret 这两个就是凭证 + .withClient("clientapp") + .secret(passwordEncoder.encode("112233")) + // 重定向地址,用于授权成功后跳转 + .redirectUris("http://localhost:9001/callback") + // 授权码模式 + .authorizedGrantTypes("authorization_code") + // 权限细分 + .scopes("read_userinfo", "read_contacts"); + } +} +``` + +##### Security Web安全配置 + +```java +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.requestMatchers() + .antMatchers("/login", "/oauth/authorize") + .and() + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .formLogin() + .permitAll() + .and() + .csrf() + .disable(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("jerry") + .password(passwordEncoder().encode("xyz")) + .roles("USER"); + } +} +``` + + +#### 测试 + +整个过程请看视频 + +https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4 + +1. 获取授权 + ``` + # 浏览器请求地址 + http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo + ``` +2. 使用授权码获取 Token + ![](https://res.cloudinary.com/incoder/image/upload/v1594911902/blog/authcode.png) +3. 请求资源服务(业务请求) + ![](https://res.cloudinary.com/incoder/image/upload/v1594912116/blog/biz-request.png) + +如果你没有安装 [Postman](https://www.postman.com),对使用 `curl` 命令比较熟悉,那么可替换上面第 2,3 步操作 + +* 使用授权码获取 Token + ```bash + # 请自行更换 code 参数的值 + curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo" + ``` +* 请求资源服务(业务请求) + ```bash + # 请自行更换 authorization 参数 + curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530" + ``` +![](https://res.cloudinary.com/incoder/image/upload/v1594949675/blog/curl-auth.png) + +### 客户端模式 + +#### 代码实现 + +##### 授权服务器配置 + +```java +@Configuration +@EnableAuthorizationServer +public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter { + + @Resource + private BCryptPasswordEncoder passwordEncoder; + + /** + * 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、 + * 默认不支持 Resource Owner Password 授权类型, + * 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证 + * 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法 + *
    + *
  1. authorization_code:授权码模式
  2. + *
  3. password:密码模式
  4. + *
  5. client_credentials:客户端模式
  6. + *
  7. implicit:简化模式
  8. + *
  9. refresh_token:刷新 token 模式
  10. + *
+ * + * @param clients clients + * @throws Exception exception + */ + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // 在内存中,用于演示,不适用于实际生产环境 + clients.inMemory() + // withClient + secret 这两个就是凭证 + .withClient("clientapp") + .secret(passwordEncoder.encode("112233")) + // 重定向地址,用于授权成功后跳转 + .redirectUris("http://localhost:9001/callback") + // 客户端模式 + .authorizedGrantTypes("client_credentials") + // 权限细分 + .scopes("read_userinfo", "read_contacts"); + } +} +``` + +##### Security Web安全配置 + +```java +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.requestMatchers() + .antMatchers("/login", "/oauth/authorize") + .and() + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .formLogin() + .permitAll() + .and() + .csrf() + .disable(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("jerry") + .password(passwordEncoder().encode("xyz")) + .roles("USER"); + } +} +``` + +#### 测试 + +整个过程请看视频 + +https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4 + +1. 获取授权 + ``` + # 浏览器请求地址 + http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo + ``` +2. 使用授权码获取 Token + ![](https://res.cloudinary.com/incoder/image/upload/v1594911902/blog/authcode.png) +3. 请求资源服务(业务请求) + ![](https://res.cloudinary.com/incoder/image/upload/v1594912116/blog/biz-request.png) + +如果你没有安装 [Postman](https://www.postman.com),对使用 `curl` 命令比较熟悉,那么可替换上面第 2,3 步操作 + +* 使用授权码获取 Token + ```bash + # 请自行更换 code 参数的值 + curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo" + ``` +* 请求资源服务(业务请求) + ```bash + # 请自行更换 authorization 参数 + curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530" + ``` +![](https://res.cloudinary.com/incoder/image/upload/v1594949675/blog/curl-auth.png) + +### 简化模式 + +#### 代码实现 + +##### 授权服务器配置 + +```java +@Configuration +@EnableAuthorizationServer +public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter { + + @Resource + private BCryptPasswordEncoder passwordEncoder; + + /** + * 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、 + * 默认不支持 Resource Owner Password 授权类型, + * 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证 + * 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法 + *
    + *
  1. authorization_code:授权码模式
  2. + *
  3. password:密码模式
  4. + *
  5. client_credentials:客户端模式
  6. + *
  7. implicit:简化模式
  8. + *
  9. refresh_token:刷新 token 模式
  10. + *
+ * + * @param clients clients + * @throws Exception exception + */ + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // 在内存中,用于演示,不适用于实际生产环境 + clients.inMemory() + // withClient + secret 这两个就是凭证 + .withClient("clientapp") + .secret(passwordEncoder.encode("112233")) + // 重定向地址,用于授权成功后跳转 + .redirectUris("http://localhost:9001/callback") + // 授权码模式 + .authorizedGrantTypes("authorization_code") + // 权限细分 + .scopes("read_userinfo", "read_contacts"); + } +} +``` + +##### Security Web安全配置 + +```java +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.requestMatchers() + .antMatchers("/login", "/oauth/authorize") + .and() + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .formLogin() + .permitAll() + .and() + .csrf() + .disable(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("jerry") + .password(passwordEncoder().encode("xyz")) + .roles("USER"); + } +} +``` + +#### 测试 + +整个过程请看视频 + +https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4 + +1. 获取授权 + ``` + # 浏览器请求地址 + http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo + ``` +2. 使用授权码获取 Token + ![](https://res.cloudinary.com/incoder/image/upload/v1594911902/blog/authcode.png) +3. 请求资源服务(业务请求) + ![](https://res.cloudinary.com/incoder/image/upload/v1594912116/blog/biz-request.png) + +如果你没有安装 [Postman](https://www.postman.com),对使用 `curl` 命令比较熟悉,那么可替换上面第 2,3 步操作 + +* 使用授权码获取 Token + ```bash + # 请自行更换 code 参数的值 + curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo" + ``` +* 请求资源服务(业务请求) + ```bash + # 请自行更换 authorization 参数 + curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530" + ``` +![](https://res.cloudinary.com/incoder/image/upload/v1594949675/blog/curl-auth.png) + +#### 测试 + +``` +http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=token&scope=read_userinfo&state=abc +``` + +``` +http://localhost:9001/callback#access_token=60fbfadc-f801-4514-a5fb-52c6fd42f6cb&token_type=bearer&state=abc&expires_in=119 +``` + +### 密码模式 + +https://github.com/spring-projects/spring-boot/issues/11136#issuecomment-381338605 + +#### 代码实现 + +##### 授权服务器配置 + +```java +@Configuration +@EnableAuthorizationServer +public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter { + + @Resource + private BCryptPasswordEncoder passwordEncoder; + + /** + * 用于配置客户端信息(id,secret,grant_type 等信息),要求至少配置一个客户端、 + * 默认不支持 Resource Owner Password 授权类型, + * 如果要使用该类型,需要在 AuthorizationServerEndpointsConfigurer 中提供 AuthenticationManager 用于用户认证 + * 授权模式系统默认提供如下方式,请查看:{@link OAuth2AuthorizationServerConfiguration.BaseClientDetailsConfiguration#oauth2ClientDetails()}方法 + *
    + *
  1. authorization_code:授权码模式
  2. + *
  3. password:密码模式
  4. + *
  5. client_credentials:客户端模式
  6. + *
  7. implicit:简化模式
  8. + *
  9. refresh_token:刷新 token 模式
  10. + *
+ * + * @param clients clients + * @throws Exception exception + */ + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // 在内存中,用于演示,不适用于实际生产环境 + clients.inMemory() + // withClient + secret 这两个就是凭证 + .withClient("clientapp") + .secret(passwordEncoder.encode("112233")) + // 重定向地址,用于授权成功后跳转 + .redirectUris("http://localhost:9001/callback") + // 授权码模式 + .authorizedGrantTypes("authorization_code") + // 权限细分 + .scopes("read_userinfo", "read_contacts"); + } +} +``` + +##### Security Web安全配置 + +```java +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.requestMatchers() + .antMatchers("/login", "/oauth/authorize") + .and() + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .formLogin() + .permitAll() + .and() + .csrf() + .disable(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("jerry") + .password(passwordEncoder().encode("xyz")) + .roles("USER"); + } +} +``` + +#### 测试 + +整个过程请看视频 + +https://res.cloudinary.com/incoder/video/upload/v1594910477/blog/video/auth.mp4 + +1. 获取授权 + ``` + # 浏览器请求地址 + http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo + ``` +2. 使用授权码获取 Token + ![](https://res.cloudinary.com/incoder/image/upload/v1594911902/blog/authcode.png) +3. 请求资源服务(业务请求) + ![](https://res.cloudinary.com/incoder/image/upload/v1594912116/blog/biz-request.png) + +如果你没有安装 [Postman](https://www.postman.com),对使用 `curl` 命令比较熟悉,那么可替换上面第 2,3 步操作 + +* 使用授权码获取 Token + ```bash + # 请自行更换 code 参数的值 + curl -X POST --user clientapp:112233 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=8uYpdo&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_userinfo" + ``` +* 请求资源服务(业务请求) + ```bash + # 请自行更换 authorization 参数 + curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 36cded80-b6f5-43b7-bdfc-594788a24530" + ``` +![](https://res.cloudinary.com/incoder/image/upload/v1594949675/blog/curl-auth.png) + +## 扩展 + +## 附录 + +* [芋道 Spring Security OAuth2 存储器](http://www.iocoder.cn/Spring-Security/OAuth2-learning-store/?self) +* [浅谈 OAuth 2.0 (二) - 授权类型](https://liaodanqi.me/2019/10/24/oauth-grant-type/) +* [Spring Security OAuth2](http://linyishui.top/2019111701.html) \ No newline at end of file diff --git a/source/_posts/springcloud3.md b/source/_posts/springcloud3.md new file mode 100644 index 000000000..9fb5bdb8a --- /dev/null +++ b/source/_posts/springcloud3.md @@ -0,0 +1,8 @@ +--- +title: SpringCloud(三)Security OAuth2 源码分析 +date: 2020-07-25 06:05:00 +categories: SpringCloud +tag: [SpringCloud, OAuth2] +--- + + \ No newline at end of file diff --git a/source/_posts/ssm.md b/source/_posts/ssm.md new file mode 100644 index 000000000..f511dde0a --- /dev/null +++ b/source/_posts/ssm.md @@ -0,0 +1,59 @@ +--- +title: 构建基础SSM框架 +date: 2018-05-20 09:39:10 +categories: Frame +tag: + - Spring + - SpringMVC + - Mybatis +--- + +## SSM结构 + +![SSM](https://res.cloudinary.com/incoder/image/upload/v1528039004/blog/ssm-structure.png) + + + +## SSM框架整合 + +所谓的SSM即:Spring,SpringMVC,Mybatis +* [Spring](https://spring.io):一个轻量级的框架,有很多的拓展功能,最主要的我们一般项目使用的就是IOC和AOP。 +* [SpringMVC](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html):Spring实现的一个Web层,相当于Struts的框架,但是比Struts更加灵活和强大. +* [Mybatis](http://www.mybatis.org/mybatis-3):一个持久层的框架,在使用上相比Hibernate更加灵活,可以控制SQL的编写,使用 XML或注解进行相关的配置. + +## 实战项目 + +![ssm-practice](https://res.cloudinary.com/incoder/image/upload/v1528647074/blog/ssm-practice.png) + +项目功能: +1. Spring,SpringMVC,Mybatis框架整合 +2. Create Features +3. Retrieve Features +4. Update Features +5. Delete Features + +> 项目示例:[rc-ssm](https://github.com/RootCluster/rc-ssm/tree/example) + +## 其他 + +### ajax之PUT请求 + +客户端ajax方式发送PUT请求,Tomcat默认不会对请求进行处理; +Tomcat: +1. 将请求体中的数据,封装成一个map +2. request.getParameter("fileName")就会从这个map中取值 +3. springMVC封装POJO对象时,会把POJO中的属性的值,request.getParameter("fileName") + + 解决方式: + - 方式一:Ajax发送POST请求 + Ajax中type:"POST" + data: $("").serialize()+"&_method=PUT" + + - 方式二:web配置中添加HttpPutFormContentFilter过滤器 + 1.HttpPutFormContentFilter将请求体中的数据解析包装成一个map + 2.request被重新包装,request.getParameter()被重写,从自己封装的map中取出数据 + +### 获取属性的值 + +prop修改和读取DOM原生属性的值 +attr修改和读取自定义属性的值 diff --git a/source/_posts/summary-2019.md b/source/_posts/summary-2019.md new file mode 100644 index 000000000..ca740e145 --- /dev/null +++ b/source/_posts/summary-2019.md @@ -0,0 +1,66 @@ +--- +title: 复盘 2019 —— 安全上车 +date: 2020-01-22 09:30:10 +categories: Summary +tag: + - Summary +password: 1024,.ok +abstract: 这是一篇加密博文,请输入密码后查看 +message: 这里需要密码才能访问 +wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试…… +--- + +回顾 2019 最重要一点是角色的转变,由客户端开发者转变为服务端开发者,虽然两者都是编码,但是在本质上有了区别,客户端面向的是用户,服务端面向的是客户端开发者,技能上由原来的单一 Android 系列上升到,整个 Spring 生态,和 Linux 体系,思想上由简单上升到高性能,高并发,函数式编程的思想。经过 5 个月的摸索和学习,还算是安全上了服务端开发的车,这里感谢那些帮助我的同事和朋友([大师傅](https://blog.dazhidayong.cn/),[大蛇丸](https://ceaser.wang/),[卡卡西](https://kaifa.dev/)),谢谢你们不厌其烦的为我指明道路和方向 + +19 年涉及的技术领域主要是 Java,Netty,Flowable,SpringBoot这几个领域,客户端主要涉及二维码,以及项目开发基础框架整合和移动端开发技能栈总结 + + + +## 技能 + +* [Java](https://java.incoder.org),项目:[Java-Skill-Stack](https://github.com/BladeCode/Java-Skill-Stack) +* [Mobile](https://mobile.incoder.org/),项目:[Mobile-Skill-Stack](https://github.com/BladeCode/Mobile-Skill-Stack) +* [Netty](https://incoder.org/tags/Netty/),项目:[rc-cluster-netty](https://github.com/RootCluster/rc-cluster-netty) +* [Flowable](https://incoder.org/tags/Flowable/),项目:[rc-cluster-flowable](https://github.com/RootCluster/rc-cluster-flowable) +* [SpringBoot](https://incoder.org/tags/SpringBoot),项目:[rc-cluster-springboot](https://github.com/RootCluster/rc-cluster-springboot) +* [Zxing](https://incoder.org/tags/Zxing/),项目:[rc-android-zxing](https://github.com/RootCluster/rc-android-zxing) +* 项目:[rc-android-mvc](https://github.com/RootCluster/rc-android-mvc) +* 项目:[rc-android-mvp](https://github.com/RootCluster/rc-android-mvp) + +[开源组织](https://archive.twodragonlake.com/)相关,完成组织基础 jar 的 maven 中央仓库托管 + +经典微服务等面向服务架构思想[技术文献翻译](https://incoder.org/categories/Translation/) + +## 阅读 + +本年也主要是阅读与技术相关的书籍,有部分书籍还未完全读完,关于**技术相关**的书籍,自己读书方式如下 +* 第一遍阅读不需要读懂每一个知识点,只需要将技术体系有一个初步的概要轮廓, +* 第二遍需要仔细阅读,并弄懂之前遇到的一系列问题, +* 第三遍是站在全局的角度把知识串联起来,和第一遍的知识概要形成呼应 + +阅读的过程中记录好笔记,然后从书上学到的思想或是技能一定要在项目中去实践运用,最后将实践和总结以不同的方式输出(可以是博客或公众号等)定期回顾 + +* [《App 架构师》](https://book.douban.com/subject/30215761/) +* [《Kotlin 极简教程》](https://book.douban.com/subject/27135841/) +* [《Spring 实战》](https://book.douban.com/subject/26767354/) +* [《SpringBoot思想编程》](https://book.douban.com/subject/33390560/) +* [《深入浅出 Mybatis 技术原理与实践》](https://book.douban.com/subject/26858114/) +* [《少有人走的路》](https://book.douban.com/subject/1775691/) +* [《从0到1》](https://book.douban.com/subject/26297606/) +* [《支付战争》](https://book.douban.com/subject/26324497/) + +## 运动 + +完成预定计划一次半程马拉松(横店马拉松),一次全程马拉松(杭州马拉松),以及全年骑行,步行,跑步等运动量目标 + +>马拉松是检验自我极限的一种方式 + +## 生活 + +* 参加 2019 年 [Google Developer Days](https://incoder.org/2019/09/12/gdd-2019/) +* 参加[十四届中国Linux内核开发者大会](https://mp.weixin.qq.com/s/JSKO_BfB-j850C1D0DP4Hw) +* 杭州 Dubbo 线下交流 + +反思 19 年的成长,没有当初那般的浮躁和不安,更多的是自我的反省和校对,少了些许的直言不讳,总之不忘初心,迎接 2020 年的挑战 + +Life's a struggle(生命不息,折腾不止,当然不能是瞎折腾) \ No newline at end of file diff --git a/source/_posts/syncing-a-fork.md b/source/_posts/syncing-a-fork.md new file mode 100644 index 000000000..3523bd009 --- /dev/null +++ b/source/_posts/syncing-a-fork.md @@ -0,0 +1,109 @@ +--- +title: Git 同步 Fork 项目 +date: 2018-08-01 16:09:50 +categories: Git +tag: Syncing +--- + +[Github](https://www.github.com) 全球最大的同性交友网站,这里拥有最前沿的IT技术创新,拥有最流行的开源项目,等等...,总之这里是我的知识仓库,每天都会在上面寻找,学习知识 + +扯远了,本篇解决对于fork的项目,如何进行源项目的更新和同步问题 + + + +## 远程仓库 + +1. 查看fork项目的远程仓库信息 + ```bash + git remote -v + origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch) + origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push) + ``` +2. 设置源项目仓库地址 + ```bash + git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git + ``` +3. 检查远程地址信息 + ```bash + git remote -v + origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch) + origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push) + upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (fetch) + upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (push) + ``` + +## 同步源仓库信息 + +1. 获取源仓库更新 + ```bash + git fetch upstream + remote: Counting objects: 75, done. + remote: Compressing objects: 100% (53/53), done. + remote: Total 62 (delta 27), reused 44 (delta 9) + Unpacking objects: 100% (62/62), done. + From https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY + * [new branch] master -> upstream/master + ``` +2. 查看本地master分支 + ```bash + git checkout master + Switched to branch 'master' + ``` +3. 合并源仓库更新到本地master分支 + ```bash + git merge upstream/master + Updating a422352..5fdff0f + Fast-forward + README | 9 ------- + README.md | 7 ++++++ + 2 files changed, 7 insertions(+), 9 deletions(-) + delete mode 100644 README + create mode 100644 README.md + ``` + +## 同步源仓库branch + +在git中master实质是一个特殊的branch,其它的branch的同步和master同步操作并不一样 + +```bash +# 查看项目的所有分支 +git branch -v +# 当前项目在master分支,origin/HEAD类似指针,表示项目默认分支是origin/master +# origin/dev,origin/i18n,origin/ivan/feat-custom-lang,origin/master这四个分支是fork项目目前拥有分支 +# upstream/dev,upstream/i18n,upstream/master表示源仓库项目所拥有的分支 +* master + remotes/origin/HEAD -> origin/master + remotes/origin/dev + remotes/origin/i18n + remotes/origin/ivan/feat-custom-lang + remotes/origin/master + remotes/upstream/dev + remotes/upstream/i18n + remotes/upstream/master + +# 切换到dev分支,同步源仓库dev分支到fork项目的dev分支 +git checkout -b dev upstream/dev +# 推送修改到fork项目dev分支 +git push origin dev +``` + +> 如果源仓库分支已被删除,那么可以在fork项目中删除源仓库已被删除的分支 +```bash +# 删除指定分支,并推送到远程仓库 +git push origin --delete branch_name +``` + +## 同步源仓库tag + +```bash +# 获取源仓库的tag +git fetch upstream --tags +# 将新的的tag推送到fork项目 +git push --tags +``` + +## 附录 + +* [同步你的 Fork 仓库](http://wiki.jikexueyuan.com/project/github-basics/fork-synced.html) +* [Configuring a remote for a fork](https://help.github.com/articles/configuring-a-remote-for-a-fork/) +* [Syncing a fork](https://help.github.com/articles/syncing-a-fork) \ No newline at end of file diff --git a/source/_posts/time1.md b/source/_posts/time1.md new file mode 100644 index 000000000..69c06385a --- /dev/null +++ b/source/_posts/time1.md @@ -0,0 +1,132 @@ +--- +title: 时间(一)之基础概念 +date: 2020-04-07 10:01:00 +categories: JDK8 +tag: [JDK8, Time] +--- + +{% cq %} +**时间**是一种尺度,在物理定义是标量,借着时间,事件发生之先后可以按 过去-现在-未来 之序列得以确定(时间点),也可以衡量事件持续的期间以及事件之间和间隔长短(时间段) —— 维基百科 +{% endcq %} + + + +

+ + +## 单位 + +时间的基本国际单位是**秒**。定义一秒为 *铯-133原子* 基态两个超精细能级间跃迁辐射振荡9,192,631,770周所持续的时间,其起点为世界时1958年的开始 + +## 时区 + +时区(Time Zone)是地球上的区域使用同一个时间定义。1884年在华盛顿召开国际经度会议时,为了克服时间上的混乱,规定将全球划分为24个时区(东西各 12 个时区)。造成时间上的混乱是由于世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差(这个偏差我们通常叫做时差) + +![图片来自维基百科](https://res.cloudinary.com/incoder/image/upload/v1586272206/blog/time-zone.png) + +### 理论时区 + +理论时区以被 15 整除的经线为中心,向东喜两侧延伸 7.5°,即每 15°划分为一个时区。理论时区的时间采用其中央经线(或标准经线)的地方时。所以每差一个时区,区时相差一个小时,相差多少个时区,就相差多少小时 + +### 法定时区 + +为了避开国界线,有的时区的形状并不规则,而且比较大的国家以国家内部行政分机型为时区界线。例如,中国跨5个时区,但为了使用方便简单并且全国统一使用一个区时,实际上在中国使用东8区的区时一般称为北京时间作为标准时间 + +## GMT + +格林尼治平均时间(英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。 +自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。 +格林尼治标准时间的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时 **基于天文观测本身的缺陷**,已经被原子钟报时的协调世界时(UTC)所取代。 + +## UTC + +协调世界时(英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称UTC)是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林威治标准时间。 + +中华人民共和国采用ISO 8601:2000的国家标准GB/T 7408-2005《数据元和交换格式 信息交换 日期和时间表示法》中亦称之为协调世界时。 + +协调世界时是世界上调节时钟和时间的主要时间标准,它与0度经线的平太阳时相差不超过1秒,并不遵守夏令时(**由实验室用足够精确的铯原子钟导出的时间作为原子时,原子时的精确度极高,精度可以达到每2000万年才误差1秒**)。协调世界时是最接近格林威治标准时间(GMT)的几个替代时间系统之一。对于大多数用途来说,UTC时间被认为能与GMT时间互换,但GMT时间已不再被科学界所确定。 + +## DST + +夏时制(英文:Daylight Saving Time),又称夏令时、日光节约时间,是一种在夏季月份牺牲正常的日出时间,而将时间调快的做法。通常使用夏时制的地区,会在接近春季开始的时候,将时间调快一小时,并在秋季调回正常时间。目前中国已经弃用DST + +## ISO-8601 + +国际标准ISO 8601,是国际标准化组织的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是2004年12月1日发行的第三版“ISO8601:2004”以替代1998年的第一版“ISO8601:1998”与2000年的第二版“ISO8601:2000”。 + +## 计算机中的时间 + +### JSR-310 + +JSR(Java Specification Requests)是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。310 是一个编号,在 JDK8 中通过这个标准提供了新的改进日期时间的 API + +相信做 Java 开发,对于JDK 的时间 API(小于JDK8版本)肯定都是吐槽不少,主要问题体现在以下几个方面 + +1. 最开始,Date 既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂 + 而后 JDK1.1 开始,将三项职责分开了 + 1. 使用 Calendar 类实现日期和时间字段之间的转换 + 2. 使用 DateFormat 类来格式化和分析日期字符串 + 3. Date 只用来承载日期和时间信息 + + 尽管已经区分了各自的职责,但在使用时任然是很不方便 +2. 谜之 year 和 month + ```java + // 定义的月是 0-11表示 1-12 月 + + Date date = new Date(2020, 4, 8); + // 输出结果:Sat May 08 00:00:00 CST 3920 + // 年竟然是 3920 = 2020 + 1900 + // 月竟然是 May = 4 + 1 + System.out.println(date); + + Calendar calendar = Calendar.getInstance(); + calendar.set(2020, 4, 8); + // 输出结果:Fri May 08 11:27:11 CST 2020 + // 年输出:和预想一致 + // 月输出:还和 Date 一样是输入月份 +1 + System.out.println(calendar.getTime()); + ``` +3. Date 与 Calendar 类中的所有属性是可变的,线程不安全 + ```java + // 计算两个日期之间的天数 + public static void main(String[] args) { + Calendar birth = Calendar.getInstance(); + birth.set(1975, Calendar.MAY, 26); + Calendar now = Calendar.getInstance(); + System.out.println(daysBetween(birth, now)); + // 连续计算两个 Date 实例的话,第二次会取得 0,因为 Calendar 状态是可变的 + System.out.println(daysBetween(birth, now)); // 显示 0? + } + + public static long daysBetween(Calendar begin, Calendar end) { + long daysBetween = 0; + while(begin.before(end)) { + begin.add(Calendar.DAY_OF_MONTH, 1); + daysBetween++; + } + return daysBetween; + } + ``` + +>JSR 310的规范领导者 Stephen Colebourne,同时也是 [Joda-Time](https://www.joda.org/joda-time/) 的创建者,JSR 310是在Joda-Time的基础上建立的,参考了绝大部分的API + +### 2038年问题 + +![图片来自维基百科](https://res.cloudinary.com/incoder/image/upload/v1586313844/blog/year_2038_problem.gif) + +现时大部分使用UNIX的系统都是32位的,即它们会以32位有符号整数表示时间类型time_t。因此它可以表示136年的秒数。表示协调世界时间1901年12月13星期五20时45分52秒至2038年1月19日3时14分07秒(二进制:01111111 11111111 11111111 11111111,0x7FFF:FFFF),在下一秒二进制数字会是10000000 00000000 00000000 00000000(0x8000:0000),这是负数,因此各系统会把时间误解作1901年12月13日20时45分52秒(亦有可能回归到1970年)。这时可能会令软件发生问题,导致系统瘫痪 + +当前的解决方案 +把系统由32位转为64位系统。在64位系统下,此时间最多可以表示到292,277,026,596年12月4日15时30分08秒 + +## 附录 + +* [时间 • 维基百科](https://zh.wikipedia.org/wiki/%E6%97%B6%E9%97%B4) +* [时区 • 维基百科](https://zh.wikipedia.org/wiki/%E6%97%B6%E5%8C%BA) +* [UNIX时间 • 维基百科](https://zh.wikipedia.org/wiki/UNIX%E6%97%B6%E9%97%B4) +* [ISO 8601 • 维基百科](https://zh.wikipedia.org/wiki/ISO_8601) +* [格林尼治标准时间(GMT) • 维基百科](https://zh.wikipedia.org/wiki/%E6%A0%BC%E6%9E%97%E5%B0%BC%E6%B2%BB%E6%A8%99%E6%BA%96%E6%99%82%E9%96%93) +* [协调世界时(UTC) • 维基百科](https://zh.wikipedia.org/wiki/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6) +* [计算机世界中的时间概念](https://blog.csdn.net/taolong1/article/details/20742613) +* [JSR 310: Date and Time API](https://jcp.org/en/jsr/detail?id=310) +* [JSR310-新日期API(完结篇)-生产实战](http://throwable.club/2020/03/02/java-jsr310-in-action/) \ No newline at end of file diff --git a/source/_posts/time2.md b/source/_posts/time2.md new file mode 100644 index 000000000..c5ca41254 --- /dev/null +++ b/source/_posts/time2.md @@ -0,0 +1,268 @@ +--- +title: 时间(二)之核心类 +date: 2020-04-08 22:01:00 +categories: JDK8 +tag: [JDK8, Time] +--- + +上一篇[时间(一)](https://incoder.org/2020/04/07/time1/)文章,我们已经了解学习了时间相关的一些概念和时间相关的其他知识,那么本篇文章开始我们深入在计算机领域中关于时间的相关核心类的知识,先来让我们看看 JSR-310(JDK8+) 和之前(JDK7 之前)的比较 + +![](https://res.cloudinary.com/incoder/image/upload/v1586423539/blog/java-time-vs.png) + + + +## JDK7 + +### Date + +Date 是 Java 最早提供用来封装日期时间的类,由于不易于国际化且参数计算不符合日常认知或不正确,很多获取年、月、日、小时等数据的方法都已废弃(@Deprecated),被 Calendar 类的方法替代 + +Date 类有两个关键的成员变量 +```java +// 记录当前时间戳 +private transient long fastTime; + +/* + * If cdate is null, then fastTime indicates the time in millis. + * If cdate.isNormalized() is true, then fastTime and cdate are in + * synch. Otherwise, fastTime is ignored, and cdate indicates the + * time. + * cdate 对象是 BaseCalendar.Date 类,继承自 sun.util.calendar.CalendarDate 包含很多已计算好的日期时间相关变量,如 dayOfWeek(所在星期的第几天),leapYear(是否闰年)等 + * 如果 cdate 对象为空,用 fastTime 变量代表精确到毫秒的时间 + * 如果 cdate.isNormalized() 方法返回 true,则 fastTime 和 cdate 已经同步过 + * 如果 cdate.isNormalized() 方法返回 false,则忽略 fastTime 的值,使用 cdate 代表时间 + */ +private transient BaseCalendar.Date cdate; +``` + +Date 类提供了两个构造函数 +```java +// 无参构造方法,创建当前时间的 Date 类 +public Date(){ + this(System.currentTimeMillis()); +} + +// 传入一个 Unix 时间戳,创建特定的时间 Date 类 +public Date(long date){ + fastTime = date; +} + +// 其他通过年月日创建的构造方法已被 Calendar.set() 和 DateFormat.parse() 等方法替代 +``` + +>静态方法 `System.currentTimeMillis()` 返回 UTC 时间从 1970-01-01 00:00:00 到现在的总**毫秒**数,返回类型为 **long**,也是我们最常见获取当前时间的方法 + +`java.sql.Date`,`java.sql.Time`,`java.sql.Timestamp` 类都继承自 `java.util.Date` 类,是专门用于数据库连接的,由于是继承关系,从数据结构来看它们和父类的区别不大。 + +主要区别在于 `Timestamp` 类可以表示至纳秒级,其 `fastTime` 字段从秒之后被截断,毫秒至纳秒精度保持在特有的 `nanos` 字段中,需要注意 `Timestamp` 类的纳秒进度可能是 假的 + +```java +package java.sql.Timestamp; + +/** + * Constructs a Timestamp object + * using a milliseconds time value. The + * integral seconds are stored in the underlying date value; the + * fractional seconds are stored in the nanos field of + * the Timestamp object. + * + * @param time milliseconds since January 1, 1970, 00:00:00 GMT. + * A negative number is the number of milliseconds before + * January 1, 1970, 00:00:00 GMT. + * @see java.util.Calendar + */ +public Timestamp(long time) { + super((time/1000)*1000); + nanos = (int)((time%1000) * 1000000); + if (nanos < 0) { + nanos = 1000000000 + nanos; + super.setTime(((time/1000)-1)*1000); + } +} +``` +可以看出,在 `fastTime` 字段强行截断后,进行毫秒值直接乘以 1000000 的操作后赋给了 nanos 字段,成为了 “只能表示到毫秒的纳秒级精度” 。当然,还可以通过 `setNanos(int n)` 方法给纳秒数赋精确值。 + +虽然从数据结构上看没有什么特别,但如果涉及到 Timestamp 类的父子类型转换或时间比较,就需要注意这里的“坑” +1. equals() 方法的不对称性 `java.sql.Timestamp` 类和其父类 `java.util.Date` 的 equals() 方法是不符合对称性 + ```java + public static void main(String[] args){ + Data date = new Date(); + Timestamp timestamp = new Timestamp(date.getTime()); + System.out.println(date.equals(timestamp)); + System.out.println(timestamp.equals(date)); + } + + // 输出结果 + true + false + ``` + +2. 时间比较类方法的 “异常” 现象如下,两个有毫秒之差的时间点,`after()` 方法返回不符合客观事实 + ```java + public static void main(String[] args){ + Long current = System.currentTimeMillis(); + Date t1 = new Timestamp(current); + Date t2 = new Timestamp(current + 1); + System.out.println(t2.after(t1)); + System.out.println(t2.compareTo(t1) > 0); + } + + // 输出结果 + false + true + ``` + +如果在不确定类型的情况下进行时间的比较,尽量使用 compareTo() 方法,可以保证正确性 + +### Calendar + +Calendar 类是一个日历抽象类,提供了一组对年月日时分秒星期等日期信息操作的函数,并针对不同国家和地区的日历提供了相应的子类,即本地化(比如:公历【GregorianCalendar】,佛历【BuddhistCalendar】,日本历【JapaneseImperialCalendar】等) + +从 JDK1.1 版本开始,在处理日期和时间时系统推荐使用 Calendar 类进行实现。在设计上,Calendar 类的功能要比 Date 类强大太多,而且在实现方式上也比 Date 类要复杂些 + +Calendar 类可以通过静态工厂方法或 new 子类的方式来获得实例 +1. getInstance() 方法,有四个重载方法,参数是时区和地区,如果不传会取服务器默认的时区和地区(地区的出现是专门为了区分泰国和日本) + * getInstance() + * getInstance(TimeZone zone) + * getInstance(Locale aLocale) + * getInstance(TimeZone zone, Locale aLocale) +2. 新建子类对象 + ```java + Calendar calendar = new GregorianCalendar(); + ``` + Calendar 类可以实现带时区的年月日时分秒星期等对 Unix 时间戳的转换,内部通过子类复杂的 computeTime() 方法进行计算。 + * 使用 getTime() 方法返回 java.util.Date 类型的时间 + * 使用 getTimeInMillis() 方法返回当前 Unix 时间戳 + * 通过 get(int field) 方法获取其他年月日等单独信息 + + | 常量 | 含义 | + |:-:|:-:| + | Calendar.YEAR | 年份 | + | Calendar.MONTH | 月份 | + | Calendar.DATE | 日期 | + | Calendar.DAY_OF_MONTH | 日期,和上面字段意义完全相同 | + | Calendar.HOUR | 12 小时制的小时 | + | Calendar.HOUR_OR_DAY | 24 小时制的小时 | + | Calendar.MINUTE | 分钟 | + | Calendar.SECOND | 秒 | + | Calendar.DAY_OF_WEEK | 星期几 | + | Calendar.DAY_OF_YEAR | 今年的第几天 | + + 也可以通过多个 set 重载方法设定各种值,同时,add() 方法支持对单个值的加减,从而实现时间推移的计算,传入负数即为减 + + ```java + public static void main(String[] args){ + Calendar calendar = Calendar.getInstance(); + System.out.println("当前日期: " + calendar.getTime()); + calendar.add(Calendar.DATE, 10); + System.out.println("日期+10: " + calendar.getTime()); + calendar.add(Calendar.DATE, -5); + System.out.println("日期-5: " + calendar.getTime()); + } + + // 输出结果 + 当前日期: Fri Apr 10 15:37:46 CST 2020 + 日期+10: Mon Apr 20 15:37:46 CST 2020 + 日期-5: Wed Apr 15 15:37:46 CST 2020 + ``` + +`GregorianCalendar` 对象可以直接使用 `isLeapYear(int year)` 接口判断是否闰年。需要注意两点 +1. Calendar 中 MONTH 的范围: 0-11(Calendar.JANUARY - Calendar.DECEMBER),因此为避免使用错,Calendar.JANUARY 来表示一月 +2. Calendar 中 DAY_OF_WEEK 星期日是 1,并不是我们习惯的星期一是 1 + +### SimpleDateFormat + +`SimpleDateFormat` 是一个以语言环境敏感的方式来格式化和分析日期的类,SimpleDateFormat允许选择任何用户自定义的日期时间格式来格式化 + +```java +public static void main(String[] args){ + Date date = new Date(); + System.out.println("格式化之前: " + date); + SimpleDateFormat ft = new SimpleDateFormat("z yyyy-MM-dd HH:mm:ss"); + System.out.println("格式化之后: " + ft.format(date)); +} + +// 输出结果 +格式化之前: Fri Apr 10 15:55:42 CST 2020 +格式化之后: CST 2020-04-10 15:55:42 +``` + +#### 字符含义 + +日期和时间格式由日期和时间模式字符串中的日期和时间模式字符串指定,从 `A` 到 `Z` 和从 `a` 到 `z` 的无引号字母被解释为表示日期或时间字符串组件的模式字母。可以使用单引号`'`引用文本以避免解释。"`''`"表示单引号。所有其他字符都不会被解释;它们只是在格式化期间复制到输出字符串中,或者在解析期间与输入字符串匹配。 + +定义了以下模式字母(所有其他字符`A` — `Z` 和 `a` - `z` 保留),详细说明见 `SimpleDateFormat` 类 Java Doc 中注释明 + +| 字母 | 日期或时间组成 | 说明 | 示例 | +|:-:|:-:|---|---| +| G | Era designator【是一个代号】 | Text | AD | +| y(小写) | Year【年】 | Year | 1996; 96 | +| Y | Week year【周年】 | Year | 2009; 09 | +| M | Month in year (context sensitive)【一年中的月份(上下文相关)】 | Month | July; Jul; 07 | +| L | Month in year (standalone form)【一年中的月份(独立形式)】 | Month | July; Jul; 07 | +| w(小写) | Week in year【一年中的第几周】 | Number | 27 | +| W | Week in month【每月的周】 | Number | 2 | +| D | Day in year【一年中的一天】 | Number | 189 | +| d(小写) | Day in month【每月的一天】 | Number | 10 | +| F | Day of week in month【每月的星期几】 | Number | 2 | +| E | Day name in week【星期几】 | Text | Tuesday; Tue | +| u(小写) | Day number of week (1 = Monday, ..., 7 = Sunday)
【星期几(1 =星期一,...,7 =星期日)】 | Number | 1 | +| a(小写) | Am/pm marker【上午/下午标记】 | Text | PM | +| H | Hour in day (0-23)【一天中的小时,0-23表示】 | Number | 0 | +| k(小写) | Hour in day (1-24)【一天中的小时, 1-24表示】 | Number | 24 | +| K | Hour in am/pm (0-11)【上午/下午 0-11表示】 | Number | 0 | +| h(小写) | Hour in am/pm (1-12)【上午/下午 1-12表示】 | Number | 12 | +| m(小写) | Minute in hour【一小时内】 | Number | 30 | +| s(小写) | Second in minute【分钟】 | Number | 55 | +| S | Millisecond【毫秒】 | Number | 978 | +| z(小写) | Time zone【时区】 | General time zone | Pacific Standard Time;
PST; GMT-08:00 | +| Z | Time zone【时区】 | RFC 822 time zone | -0800 | +| X | Time zone【时区】 | ISO 8601 time zone | -08; -0800; -08:00 | + +#### 格式化示例 + +给定太平洋时间,2001-07-04 12:08:56,以下示例展示按照自定的格式显示时间 + +| 日期和时间模式 | 结果 | +|---|---| +| "yyyy.MM.dd G 'at' HH:mm:ss z" | 2001.07.04 AD at 12:08:56 PDT | +| "EEE, MMM d, ''yy" | Wed, Jul 4, '01 | +| "h:mm a" | 12:08 PM | +| "hh 'o''clock' a, zzzz" | 12 o'clock PM, Pacific Daylight Time | +| "K:mm a, z" | 0:08 PM, PDT | +| "yyyyy.MMMMM.dd GGG hh:mm aaa" | 02001.July.04 AD 12:08 PM | +| "EEE, d MMM yyyy HH:mm:ss Z" | Wed, 4 Jul 2001 12:08:56 -0700 | +| "yyMMddHHmmssZ" | 010704120856-0700 | +| "yyyy-MM-dd'T'HH:mm:ss.SSSZ" | 2001-07-04T12:08:56.235-0700 | +| "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" | 2001-07-04T12:08:56.235-07:00 | +| "YYYY-'W'ww-u" | 2001-W27-3 | + +### TimeZone + +## JDK8+ + +### Clock +### Instant +### LocalDate +### LocalTime +### LocalDateTime +### OffsetTime +### OffsetDateTime +### ZonedDateTime +### ZoneId +### Year +### Month +### DayOfWeek +### MonthDay +### YearMonth + +## 总结 + +## 附录 + +* [Java SE 8 日期和时间](https://www.oracle.com/technetwork/cn/articles/java/jf14-date-time-2125367-zhs.html?printOnly=1) +* [循序渐进解读计算机中的时间—应用篇(上)](https://mp.weixin.qq.com/s/oMQ--gLOGMXpblM4g8TnZA) +* [循序渐进解读计算机中的时间—应用篇(下)](https://mp.weixin.qq.com/s/N5Kkky9MyA9b73SM3NVVEQ) +* [JSR310新日期API(二)-日期时间API](http://throwable.club/2019/01/01/java-jsr310-time-api) +* [Java中日期时间相关核心类](https://dslztx.github.io/blog/2018/09/07/Java%E4%B8%AD%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4%E7%9B%B8%E5%85%B3%E6%A0%B8%E5%BF%83%E7%B1%BB) +* [《Java 8 实战》● 第 12 章](https://book.douban.com/subject/26772632) \ No newline at end of file diff --git a/source/_posts/time3.md b/source/_posts/time3.md new file mode 100644 index 000000000..249dff0ec --- /dev/null +++ b/source/_posts/time3.md @@ -0,0 +1,16 @@ +--- +title: 时间(三)之格式化解析和时间计算 +date: 2020-04-09 09:12:00 +categories: JDK8 +tag: [JDK8, Time] +--- + + + +## 总结 + +## 附录 + +* [Convert Date to LocalDate or LocalDateTime and Back](https://www.baeldung.com/java-date-to-localdate-and-localdatetime) +* [JSR310新日期API(三)-日期时间格式化与解析](http://throwable.club/2019/01/05/java-jsr310-date-time-format-parse/) +* [Java中日期时间相关核心类](https://dslztx.github.io/blog/2018/09/07/Java%E4%B8%AD%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4%E7%9B%B8%E5%85%B3%E6%A0%B8%E5%BF%83%E7%B1%BB/) \ No newline at end of file diff --git a/source/_posts/time4.md b/source/_posts/time4.md new file mode 100644 index 000000000..4b9bfe751 --- /dev/null +++ b/source/_posts/time4.md @@ -0,0 +1,16 @@ +--- +title: 时间(四)之主流框架中使用 +date: 2020-04-10 11:11:00 +categories: JDK8 +tag: [JDK8, Time] +--- + + + +## 总结 + +## 附录 + +* [Java SE 8 日期和时间](https://www.oracle.com/technetwork/cn/articles/java/jf14-date-time-2125367-zhs.html?printOnly=1) +* [JSR310新日期API(二)-日期时间API](http://throwable.club/2019/01/01/java-jsr310-time-api/) +* [Java中日期时间相关核心类](https://dslztx.github.io/blog/2018/09/07/Java%E4%B8%AD%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4%E7%9B%B8%E5%85%B3%E6%A0%B8%E5%BF%83%E7%B1%BB/) \ No newline at end of file diff --git a/source/_posts/top1.md b/source/_posts/top1.md new file mode 100644 index 000000000..525a80023 --- /dev/null +++ b/source/_posts/top1.md @@ -0,0 +1,24 @@ +--- +title: 重要说明 +date: 2022-06-05 06:05:00 +categories: Top +tag: [Top] +sticky: 1 +--- + +距离上一次更新文章已经过去了 1 年多了,时间可过的真快。原计划将现在的主站点要进行按照领域划分,将平时工作中遇到的、实践的技术整理到对应的领域,方便快速查找,提供一个沉浸式的学习知识体验,但由于个人也是一个懒癌拖更患者,加之在过去的一年工作中处在新领域,学习了很多技术,很多笔记还没有整理完善,因此没有及时更新博客,同时最近也遇到了站点无法正常访问,经过一系列的排查,发现了是由于开源的 CDN 提供方 [jsdelivr](https://github.com/jsdelivr/jsdelivr/issues/18397) 被污染了 DNS,因此不得不先更新站点的基础服务,以便能正常访问 + + + +> 话说,国内的网络环境真的是一言难尽,不得不说在技术、技术基础设施、技术思想等发展道路上任重道远 + +对于个人几个主要的站点,规划如下 + +1. [incoder.org](https://www.incoder.org): 作为分享生活、感悟、个人状态为主的地方,偶尔汇总某些技术等的综合性文章 +2. [backend.incoder.org](https://backend.incoder.org): 记录以 Java 为基础的后端开发生态技术领域 +3. [mobile.incoder.org](https://mobile.incoder.org): 记录以原生开发为基础的移动端开发生态技术 +4. [incoder.app](https://incoder.app): 记录个人开源应用 + +{% note info %} +后续会迁移本站点部分文章到具体的领域站点 +{% endnote %} diff --git a/source/_posts/travel-cs.md b/source/_posts/travel-cs.md new file mode 100644 index 000000000..5129e7752 --- /dev/null +++ b/source/_posts/travel-cs.md @@ -0,0 +1,9 @@ +--- +title: 长沙 +date: 2020-05-01 00:11:01 +categories: [Memory, Travel] +tag: 长沙 +--- + + + \ No newline at end of file diff --git a/source/_posts/travel-cz-feel.md b/source/_posts/travel-cz-feel.md new file mode 100644 index 000000000..909286d6b --- /dev/null +++ b/source/_posts/travel-cz-feel.md @@ -0,0 +1,86 @@ +--- +title: 川藏行 —— 人在囧途 +date: 2020-10-13 11:02:00 +categories: [Memory, Travel] +tag: [成都, 拉萨] +--- + +这次假期安排算是毕业后,除去 2020 年春节因为疫情,假期最长的一个,满脑子写着高兴,以至于我出门前一刻,还在处理工作上的事情,浑浑噩噩的追下午的动车和好友汇合,哪知道这只是囧途的开始,在地铁上我快要困死过去,还好没有坐过站,之前有看一眼车站,但是并没有仔细看具体是哪个站,在我的潜意识里一直认为是东站,当我冲到检票口时,我刷身份证发现进不了站,工作人员说我跑错站,我当时一脸茫然(心想跑错站也没关系,只要它经过东站就可以),赶紧查看我的购票记录,发现自己真是跑错了车站而且还不经过东站,距离发车时间就剩 1 小时了 + + + +计算了下地铁和检票时间,得出现在赶过去时间非常紧,万一中途出现什么状况,我肯定要凉凉,立马决定改签到东站出发的列车,但是我这次购票是用的 12306 行程**积分兑换**的,不能直接在手机上进行改签或退票,于是我就赶忙拖着行李去往 1 楼的人工服务窗口,意外的是我恰巧跑向了东边的人工窗口,到了东边绕了一圈发现今天东边窗口不提供服务,于是乎我又从东边一路小跑到西边窗口,今天并不是周末,但人也是不少在排队,我必须在 1 小时之内改签车票,否则我当时购买的车次发车的话,我必须得在原车票的起点站去改签,而原车站后面到达目的地的时间太晚,我根本赶不上飞机(〒︿〒) + +这次看清了对应业务的办理窗口,我找到其中一个进行排队,焦急地等待着,不清楚前面那位朋友是什么状况,办理他一个人的时间足足等了 10 多分钟,一边看着手机查询车次,一边盯着时间,眼看就要到我了,那工作人员却说暂停服务,她要去趟卫生间,我说我很赶时间,她没理会我,此时的我进退两难,去排另一个队,时间明显不够,我也没办法去插队,两眼一直盯着在我前方的窗口,还好还好,工作人员大概耽误了 3 分多钟回到岗位上,帮我办理改签,由于我当时兑换的是动车,现在发车时间最近的一趟是高铁,需要再补 3000+积分,还好我的积分还够,那还等什么,改改改,改签完成后长舒一口气,再晚 10 分钟,我要么去原发车站去改签,要么重新购买在东站购买车票(此时我并不能在手机上购买车票,因为原行程和我从当前位置出发的行程是冲突的,必须得在会员人工窗口办理退票) + +## 12306 + +### 12306会员积分使用规则 + +![12306会员积分使用规则](https://res.cloudinary.com/incoder/image/upload/v1606645414/blog/12306-member.png) + +### 12306车票改签规则 + +![12306车票改签规则](https://res.cloudinary.com/incoder/image/upload/v1606645414/blog/12306-change-ticket.png) + +### 12306车票退票规则 + +![12306车票退票规则](https://res.cloudinary.com/incoder/image/upload/v1606645414/blog/12306-refund-ticket.png) + +改签完车票后,距离发车时间还有 1 个小时,我赶忙在候车厅二楼找到一个饭馆,去点了些东西,吃着东西,一个电话打断了我囫囵吞枣的状态,说是项目线上环境,某页面数据加载不出来,让我看看是什么原因,是不是我中午出发前改的东西影响了,我一脸问号,因为我改动的并没有涉及到页面加载慢的地方,那边说下午要线上演示,让我抽空看看处理下,我连忙说我尽量。距离检票时间还是有 30 多分钟,我放下了手里的鸡腿,开启手机热点,打开电脑,盯着屏幕今天的提交记录,一遍遍排查,自己的没有做相关的操作,当前我也没有心思定下心来找问题的根源,想着先把超时时间调大些,先保证能加载出来数据,至于慢的问题,我到达目的地后再处理,三下五除二调整后,发布测试,并没有什么用😔,于是继续找到对应 API 服务,把对应的服务接口查询做了下优化处理,想让服务正常使用,处理调试后,发现可以接受,来不及多想,赶忙收拾东西去检票上车,一路狂奔 + +到达中转站,和好友汇合,终于停下来了,接过带给我的心心念念的的芋泥奶茶,我就不客气的解决掉它,别喝边说着,快一年没见,怎么又长高了,变瘦了,你都偷偷补了啥,她弱弱的回了句,我不挑食的,我一脸尴尬,她知道我挑食,不吃肉(尤其是外面,老妈做的肉,我还是能吃点),我赶忙说到,我在努力克服这个了,现在还是能稍微吃一点的,两人在候机厅断断续续有一搭没一搭的说着今年发生的事情,说着说着,突如其来的语音播报,由于疫情影响,我们所乘航班推迟,还好没有说是取消,不然今天我要囧到家😶,按照要求填写好行程路径,在线领取健康码后(为啥全国的健康码还没统一,一脸嫌弃,不前一阵子说健康码全国通用嘛),延迟 40 分钟左右,终于登上了前往成都的飞机,在一路的折腾,飞机起飞后,夜景也没来得急看我就睡着了,到达成都后才被朋友叫醒 + +## 天府之国 - 成都 + +在这里,每件事或物都能和国宝相连,要是这里生活的话,我居然有我也是“国宝”的错觉,哈哈哈,突然有点厚脸皮了,谁让这家伙那么可爱,第一天就把市区能去的都逛了个七七八八,到底是 3 个年轻人,身体真好,哎,都没有停下来好好看看成都的美女帅哥,都光顾着吃喝和看风景了,话不多说,先来几张图片缓和下气氛,写到这里我又想起了蛋烘糕,想吃,想起了又辣又麻的椒麻鸡,想吃,想起了兔头(真是让人秃头,兔兔那么可爱,为啥要吃兔兔,其实我更喜欢兔子肉,哈哈哈),想吃。啊好难受,都想吃,我觉得我如果待在这个城市,一定能长胖 +![](https://res.cloudinary.com/incoder/image/upload/v1606656901/blog/travel/sc-panda.jpg) +![](https://res.cloudinary.com/incoder/image/upload/v1606668747/blog/travel/sc-alley.jpg) +![](https://res.cloudinary.com/incoder/image/upload/v1606668748/blog/travel/sc-street.jpg) +![](https://res.cloudinary.com/incoder/image/upload/v1606668748/blog/travel/sc-memory.jpg) +![](https://res.cloudinary.com/incoder/image/upload/v1606668748/blog/travel/sc-hot-pot.jpg) +皂片有点多,就放这几张吧,看看就好,后面两天是不是下点小雨,阴蒙蒙的天气就和我当时的心情对应上了,因为我还要修改问题,前面留下的坑,还要去填,真的,真的,真的我不是故意要把工作的事情带到旅途中来的,下次我一定不带电脑了,打死不带,刀架到我脖子也不带。让某位同学一整天都在家里刷剧,真是罪过罪过(虽然你曾经这里待了很久,尝遍了这里美食,看尽了这里的美景,但这不一样,因为和你同看风景,同品美食的是不同的人,不同的心情) + +话说回来玉林街道那里好吃的真多,那个谁自己要控制住,不然想瘦真的有点难,话说回来在成都的 3 天虽然有`亿`点点的不完美,但是正是由于这些不完美,才让我的记忆更加深刻。按照网上所谓的高评分美食,走路老远的路过去,发现吃完并没有想想中那么好吃(我严重怀疑他们是刷单刷出来的);不看清目的地营业时间,跑去傻傻的在门口不知道该往哪去(告诫我下次做规划这种事情一定提前要有备用计划);一起搭公交车去很远的地方买甜点,虽然有点小远,但是值得。 + +不知道下次会是什么时候再去成都了,也不知道下次身边会是谁,能不能像现在一样能聚在一起。我想的可真远,大白天发什么神经,还是过好眼前的,得嘞……珍惜现在拥有的 + +三天后,一行三人出发,前往西藏拉萨。我们并没有搭乘火车,而是选择了飞机,谁让这价格那么香,还能节省接近 2 天的时间。抵达拉萨后,并没有出现不良的反应 + +## 日光之城 - 拉萨 + +引入眼帘给我的第一感觉是蓝,蓝的彻彻底底,没加半点人工滤镜渲染,也没 AI 换天,这里随手拍照都能拍到非常好看的图片,不需要修图,这里的云也很配合,像是一种出淤泥而不染的干净,在蓝色背景的映衬下,云朵就像是在你的手边随手都可以去摘下来捧在手里,话不多说,我们先来看拉萨的牌面,大气恢弘的建筑俯瞰整个拉萨市(标准的游客拍照) +![](https://res.cloudinary.com/incoder/image/upload/v1606698602/blog/travel/lhasa-potala-palace.jpg) +第一天就头疼的么,不存在的,这里海拔也不过 3600 左右,还是能接受的,只不过快步走或跑步是真的比内陆容易喘气。在高原地带由于氧气稀薄,汽车大部分的燃油都没有充分燃烧,尾气直接排进空气,这种味道会让你欲仙欲死。四天的行程已提前被安排,跟着导游,听他安排 + +第二天一大早就集结,开始 4 天的行程,行程结束后再回到拉萨市区,迎着朝阳汇聚了来自不同地方 12 个小伙伴,接下来的这几天我们这些人就是一个小群体,彼此互相照顾,所幸我们所在的团队中年龄都不大,都是年轻小伙和年轻姑娘,大家之间也没什么隔阂,很快就能玩耍到一块去,下图是 07:35 这个点,这里的天还没有完全亮 +![](https://res.cloudinary.com/incoder/image/upload/v1606719263/blog/travel/lhasa-white-tower.jpg) + +我们行程的这几天天气非常好,都是蓝天白云,刚开始不停的按下快门,想要记录下这让人心旷神怡的美景,生怕自己错过了最美的时刻,但到后来都有些麻木了,原因是你只要抬头就能看到,根本拍不过来,索性就好好沉浸其中,好好呼吸这有点甘甜的空气 +![](https://res.cloudinary.com/incoder/image/upload/v1606720963/blog/travel/lhasa-nyenchen-tanglha.jpg) +![](https://res.cloudinary.com/incoder/image/upload/v1606723559/blog/travel/lhasa-sky.jpg) + +当蓝天配上纳木错的美景,是一种秀色可餐的满足感 +![](https://res.cloudinary.com/incoder/image/upload/v1606723894/blog/travel/lhasa-namtso1.jpg) +![](https://res.cloudinary.com/incoder/image/upload/v1606723894/blog/travel/lhasa-namtso2.jpg) + +当蓝天被雄鹰所占领,这就是鹰击长空现实版 +![](https://res.cloudinary.com/incoder/image/upload/v1606724168/blog/travel/lhasa-eagle.jpg) + +当所有的疲惫和不适到了珠峰大本营,都会被抛之脑后,因为我已深深被这傲视群雄的孤独所吸引,第一次与世界最最高峰是这么的近 +![](https://res.cloudinary.com/incoder/image/upload/v1606724979/blog/travel/mount-qomolangma.jpg) + +>照片实在是有点多,继续挖坑后面整理成相册再放出来 + +上一次超过 30H 的行程,那会刚毕业,一路上总能听到各式各样千奇百怪的故事,和遇到一些形形色色的路人,因为那时候,在火车上经常没有信号,大家会一起唠唠嗑,我就听着这样乱七八糟的故事和别人经历的事,不想听了就翻开自己带的书,就这样度过那漫长的行程。而现在网络普及,信号增强了,但是人与人的交流好像都被转移到线上了,在一块面对面交流的人少了,故事也都来源于那块屏幕,隔着屏幕说话,科技的初衷是提高我们的生活质量和方便我们的生活,而不是隔断人与人的面对面交流 + +## 总结 + +![](https://res.cloudinary.com/incoder/image/upload/v1606725898/blog/travel/lhasa-future.jpg) +{% centerquote %}有形的东西迟早会凋零,但只有回忆是永远不会凋零的{% endcenterquote %} + +1. 好好学一下摄影和构图吧,总会用得上 +2. 要把快乐传染给周围的人 +3. 有话就要说,别一个人闷在心里 +4. 把想到的感受到的及时记录下来,有时候灵感,想法就是那么昙花一现 +5. 多吃点肉吧,不然风一吹朋友就找不到我了 diff --git a/source/_posts/travel-cz-plan.md b/source/_posts/travel-cz-plan.md new file mode 100644 index 000000000..2109a22a8 --- /dev/null +++ b/source/_posts/travel-cz-plan.md @@ -0,0 +1,122 @@ +--- +title: 川藏行 —— 行程规划 +date: 2020-09-13 10:02:00 +categories: [Memory, Travel] +tag: [成都, 拉萨] +--- + +唠叨了很久的《成都》,这次终于要去见一见你了,成都在我的印象中是一个万物皆可辣,但也少不了让你肉跳的麻,那里有让你每天不重样的美食,有看不完的美景,也有能让你安逸的歌谣[《成都》](http://music.163.com/song?id=436514312&userid=34509906)。早在去年十一就被[《盛世中华》](https://www.bilibili.com/video/BV19E41197Kc?p=2)视频种草,时隔一年,这个心愿要就要被实现,这次的十一目标不仅是四川,还有西藏的风景,这是双份的快乐。 + +曾经记一个人是一篇文章,后来记一个人是一首歌,后来记一个人是一门学科,再后来记一个人是一段代码或语言,到现在记一个人是一个城市,因为那是有你的城市,那里有你的故事,好了废话有点多,先来看看这次出行的规划 + + + +## 四川 + +总计 3 天时间(2020.09.26 —— 2020.09.28),尽管《盛世中华》中的风景很迷人,但并不在此次行程安排中,主要是考虑到这些景点都比较远,且并不是成熟的游玩路线,这次去的地方主要是视频中没有的景点 + +### DAY 1 + +第一天主要是在成都市区内游玩,景点包括:锦里,杜甫草堂博物馆,人民公园,以及四川大学望江校区或者她曾读书的西南财经,其实这都不是重点,重点是去吃各种美食(The Sense 醒食,無早 bowl,老龙亭,还有各种小吃和必吃的火锅),对了还要见一个 3 年多未见的老友(团座) + +### DAY 2 + +第二天去一个稍微远点的地方,乐山大佛,小时候看《风云》时还以为那尊大佛是电脑特效,后来才知道这是真的,那我可要去那里找一找有没有聂风留下的雪饮刀,和菩提果,修炼我的武功。如果有时间去爬一爬峨眉山,去看看令狐冲是否还在那里带领峨眉派,那里有没有周芷若留下倚天剑的残骸 + +### DAY 3 + +第三天安排上国宝,这圆滚滚的大家伙,好想用手 rua rua 它的毛,行程结束后再去 mars 上推荐的书店(文轩 BOOKS,無早 bookstore),在那里看一看书,吃一吃甜点,品一品生活,晚上可以去建设路,去感受下城市烟火 + +## 拉萨 + +共计 8 天(2020.09.29 —— 2020.10.06),拉萨由于是非常陌生,因此选择跟团游的方式,这样既节省时间又省钱(我只是一个搬砖工人),因此选择了 5 天 4 夜(DAY 2 —— DAY 6)的跟团游玩,极大可能是走马观花式标准的游客方式 + +![路线参考](https://res.cloudinary.com/incoder/image/upload/v1599987038/blog/travel/%E8%A5%BF%E8%97%8F%E8%B7%AF%E7%BA%BF.png) + +* 以拉萨为中心,由近及远,最负盛名的纳木错(路程较远,往往需要在湖边村镇留宿一晚)和羊卓雍措(可当他往返) +* 向南,山南人文线,相对比较小众,山南是西藏的“旧都”,人文圣地估计众多,包括西藏第一个真正意义上的寺院:桑耶寺,常见的路线是 2 天,包含了拉姆拉错,思金拉错以及格鲁三大寺当中最古老的甘丹寺(在拉萨达孜县) +* 向东去往林芝,这里衔接川滇,海拔较低,森林茂盛,有西藏小江南美陈,全程会路过巴松措,鲁朗林海,雅鲁藏布江(峡谷),南迦巴瓦峰,3 天的行程,再加 2 天可抵达波密,然乌湖 +* 向西南是日喀则,这里是后藏的中心,古今地位仅次于拉萨,除了在此拜访扎什伦布斯之外,跟多人选择来到这得目的是向南 200 公里走进珠峰大本营,去看一下世界第一高峰,常规路线会带上纳木错,往返总计 5 天 +* 向西北,是“西藏的西藏”阿里一线,人迹罕至,痛并快乐,经过纳木错穿越羌塘无人区,抵达狮泉河,冈仁波齐,古格王朝,玛旁雍错,常规路线 7—10 天 +>贪心路线,从拉萨出,去程走阿里线,回程走日喀则线,一个西部大环线,耗时 13—15 天 + +当然上面是整个拉萨可以游玩的框架,而我们小团队并不是和上图完全一致,只是其中的一部分 + +### DAY 1 + +由于是第一次来高原地带,所以第一天就不进行大运动量的活动,就在拉萨市区走一走,了解藏族风土人情,大致包括以下:布达拉宫,文成公主,八廓街,大昭寺,总之看心情和看身体状况来灵活应变 + +### DAY 2 + +行程安排:拉萨 - 念青唐古拉山 - 纳木错圣象天门日出日落 - 住圣象天门 +生活住宿:早餐自理,午餐、晚餐含,住营地帐篷或附近民宿(住宿环境别抱太大幻想) +重点关注:纳木错圣象天门 + +### DAY 3 + +行程安排:圣象天门 - 那根拉山口 - 藏北草原 - 东古拉山 - 尼木 - 日喀则 +生活住宿:早餐、午餐、晚餐含,住三星酒店 +重点关注:藏北草原 + +### DAY 4 + +行程安排:日喀则 - 定日 - 嘉措拉山口/加乌拉山口 - 珠穆朗玛国家公园 - 珠峰大本营 - 民宿/营地帐篷 +生活住宿:早餐、午餐、晚餐含,住营地帐篷或附近民宿(住宿环境别抱太大幻想) +重点关注:珠峰大本营的星空 + +### DAY 5 + +行程安排:定日 - 扎什伦布寺 - 日喀则 +生活住宿:早餐自理,午餐、晚餐含,住三星酒店 +重点关注:日喀则 + +### DAY 6 + +行程安排:日喀则 - 卡若拉冰川 - 羊卓雍措 - 拉萨 +生活住宿:早餐、午餐含,晚餐自理,晚上返回拉萨 +重点关注:羊卓雍措 + +### DAY 7 —— DAY 8 + +这两天是自由时光,没有特殊的安排 + +## 注意事项 + +由于此次去往西藏相对比较偏远,加之藏族人民的生活语言等和我们都不一样,生活环境和其他地方相差较大,异常在出行前带好一些必备的用品, + +### 行李指南 + +1. 证件,一次性洗漱用品,水杯等 +2. 阿咖酚散与葡萄糖粉,有效缓解高反,晕车药/贴,感冒发烧,消炎等药物 +3. 防风防雨的衣服很有必要,羽绒服,保暖衣物等 +4. 进藏四件套:帽子,墨镜,防嗮,唇膏 +5. 相机等电子设备 +6. 带一些速食,比如泡面,辣条,自住小火锅,矿泉水等 + +>可根据自身需要合理的补充 + +### 何时进藏 + +1. 6-8 月份为西藏雨季,景色会打折扣 +2. 最佳:9-10 月(秋叶),3-4 月(春桃) + +### 交通方式 + +>根据自身条件合理选择 + +1. 包车:灵活度高,价格贵(±¥1500/天) +2. 拼车:同车人数少,但可能气场不合 +3. 自驾:路况复杂且不能高反,要求极高 +4. 跟团:12 人左右,靠窗好位置靠抢 +5. 大巴:只去成熟景区和各大购物店 + +## 参考 + +1. [Mars](http://www.yohomars.com/) +2. [盛世中华](https://www.bilibili.com/video/BV19E41197Kc?p=2) +3. [86元打卡成都必吃小吃](https://www.bilibili.com/video/BV1Sk4y1y7Zz?from=search&seid=6161260675537377037) +4. [成都·VLOG·下](https://www.bilibili.com/video/BV1Uz4y1X7xB) +5. [绝对旅行指南 - 成都篇](https://www.bilibili.com/video/BV1LT4y1L7kH) +6. [绝对旅行指南 - 西藏篇](https://www.bilibili.com/video/BV1zk4y1y7hc) +7. [跨越山海看珠峰](https://www.bilibili.com/video/BV1NZ4y1N7en) + diff --git a/source/_posts/travel-hs.md b/source/_posts/travel-hs.md new file mode 100644 index 000000000..9bacfe2f4 --- /dev/null +++ b/source/_posts/travel-hs.md @@ -0,0 +1,40 @@ +--- +title: 忆·黄山 +date: 2018-05-01 00:02:00 +categories: [Memory, Travel] +tag: 黄山 +--- + +{% cq %}黄山归来不看岳{% endcq %} + +五岳未归,先品黄山。以前看黄山还是小学课本《黄山》一文介绍黄山的美,黄山的秀丽,黄山的与众不同,这次是亲身去体验黄山的姿态;趁着五一,趁着年轻,趁着...。废话不多讲,先看黄山日出美景 + + + + + +>别问我为啥抖,没有支撑点,全程手持...逃 + +这次黄山之行并没有做任何功课,计划到实施前后不超过15天,抱着走一步,看一点的心态去玩,没想到五一节假日,来黄山的人不是很多。 + +## 出行方式 + +杭州 **城西客运站** 做大巴直达黄山景区,票价:¥110,时间:大约4小时左右到达 + +## 攻略 + +逃,没有...... +由于到达黄山游客集散中心已是14:00,由于距离黄山还有10多公里,你可以走路去黄山山脚下,而且16:00之后没有大巴去黄山景区。因此随便找了个地吃完中午饭,就往乘大巴车黄山景区去了(¥12/人),由于上山的入口有好几个,我们也没有去研究,大巴到 **云谷寺** 景区,我们也就下车从这里出发往山上去了,你可以坐缆车去往山顶,我们一行三人,选择了徒步上山,对了门票:¥230/人 + +一路说说笑笑,也没有预订上山的旅店,我们心真大,刚走了没多久,就看到了两个人被交椅抬着下山了,其中一个应该是摔了,头破血流的样子,还没开始,就...;没多管,一路还是很轻松,毕竟都是年轻人,体力不错,走到 **白鹅岭** 已经开始下雨,雨越下越大,因为在边走边看的路上,我们决定来黄山当然是去 **迎客松** 的景点,然后我们顺着 **白鹅岭** 前往 **白鹅山庄旅游商场** 去避雨,然后是人多的无法挪开脚,此时天色已晚,我们稍作休息,找了半天也没有能睡得地,那床都是人挤人。我们找了个茶馆,吃了些带着的食品,喝了一小时茶,大约20:00左右,我们决定,今晚夜行到 **迎客松** + +雨后起了大雾,山顶那时雾色正浓,能见度大约在3米。我们三人也紧随其形,在 **光明顶** 片区玩了一会,这里看日出不错,当我们并没有这里等日出,毕竟这里离 **迎客松** 有一小时多的行程,我们要明天早早的在 **迎客松** 那里拍照装逼,拍完照然后回走去最高峰 **莲花峰** ,然而到了 **迎客松** 才发现,并不像电视上看到的,是在山的悬崖边。好了,这会才23:00多,怎么办,还有好几小时,又没有帐篷什么地可住,三人就在这 **迎客松** 前的广场上,发现了超大遮阳伞两把,哈哈哈,我们就用遮阳伞前后堵住,加上自己的雨伞,构建了一个堡垒,这下,我们三可用在里面睡觉了,雨后的山上很潮湿,就这样半将半究的,坚持到4点多。 + +天快要亮了,要找地儿去拍日出,我答应别人了,要发日出照片给她,往回走去 **莲花峰** 那里并不合适,更重要的是山路也被封,不上上去,只好找到 **玉屏索道** 的另一条路上,这里刚刚好可用看到日出 + +![日出](https://res.cloudinary.com/incoder/image/upload/v1528023909/blog/travel/travel-huangshan-sunrise.jpg) + +拍完日出,我们快速折回到 **迎客松** ,那里已经开始有三三两两的人了,我们动作要快,否则等会从索道上来大批人马,嗯,快速装逼完成,迅速撤离战场 + +![迎客松](https://res.cloudinary.com/incoder/image/upload/v1528023881/blog/travel/travel-huangshan-yks.jpg) + diff --git a/source/_posts/travel-zjj.md b/source/_posts/travel-zjj.md new file mode 100644 index 000000000..aa68fe466 --- /dev/null +++ b/source/_posts/travel-zjj.md @@ -0,0 +1,121 @@ +--- +title: 行·张家界 +date: 2018-05-20 22:39:10 +categories: [Memory, Travel] +tag: 张家界 +--- + +{% note %} +* 时间:2018.06.16——2018.06.19 +* 地点:杭州——张家界 +* 目标:武陵源景区,天门山景区,大峡谷景区 +{% endnote %} + +听说张家界是人间仙境,鬼斧神工,嗯,今年端午就去一探究竟,慌慌张张,匆匆忙忙做一份旅行攻略,翻遍百度,爬烂谷歌,都没有找到匹配的攻略,哎,可能是我姿势不对?! + +张家界,张家界景区共分为四块:**张家界国家森林公园**,**杨家界自然保护区**,**天子山自然保护区**,**索溪峪自然保护区**四大景区,统称为武陵源风景名胜。 + + + +**最受欢迎** 的四大景区 +1. 武陵源景区(森林公园、金鞭溪、袁家界、杨家界、天子山、十里画廊等) +2. 天门山景区(亚州最长的索道、世界公路奇观、玻璃栈道等) +3. 大峡谷风景区(新开发的玻璃桥) +4. 凤凰古城 + +## 出行准备 + +1. 身份证件等相关证件 +2. 数码产品,雨具等 +3. 简单洗漱用品及换洗衣物 +4. 现金若干(不必太多) +5. 零食(必备:辣条) + +## 注意事项 + +由于是自由行的方式,因此提醒以下几点 +1. 到达张家界后,**拒绝** 和 **一切** 人搭话,避免一些麻烦,给行程带来不愉快 +2. 保管好自己的物品 +3. 张家界火车站出站后即可到汽车站乘坐大巴去武陵源景区,50分钟左右,10(森林公园)—12元(武陵源) +4. 大峡谷,天门山景区玻璃桥都需要提前5天在网上预定 + +## 出行路线 + +### 整体路线图 + +路线一: + * Day1(16):天门山景区 + * Day2.Day3(17-18):武陵源景区 + * Day4(19):大峡谷风景区 + +路线二(推荐) + * Day1.Day2(16-17):武陵源景区 + * Day2.Day3(18):大峡谷风景区 + * Day4(19):天门山景区 + +![扼要路线图](https://res.cloudinary.com/incoder/image/upload/v1527342618/blog/gitpages-zjj-road.png) + +{% note info %} +标记说明 +1. 张家界火车站 +2. 武陵源景区 +3. 大峡谷风景区 +4. 天门山景区 +{% endnote %} + +### 武陵源景区路线 + +门票:245 元+保险费3 元(3天内多次进出有效,含环保车票价) +开放时间:8:00-17:00 +Day1:森林公园-金鞭溪-杨家界 +Day2:大观台-天子山-十里画廊-索溪湖-武陵源门票站 +从森林公园进,从武陵源出,不走回头路。需要在 **丁香榕** 住一宿 +![武陵源景区](https://res.cloudinary.com/incoder/image/upload/v1527324112/blog/gitpages-zjj.gif) + +### 大峡谷风景区路线 + +门票:大峡谷(门票122元)+玻璃桥(门票138元) +开放时间:08:00-17:00 +Day3:玻璃桥-大峡谷 + +### 天门山景区路线 + +门票:258.00元(含往返索道、环保车)【旺季】 +开放时间:08:00~16:00 +路线Day4:玻璃栈道-天门山寺-天门洞(坐索道上山顶——走西线——再到天门翻水处坐自动扶梯到天门洞——爬999级阶梯——最终坐环保车返回至市区) +**自备中午餐** + +![天门山景区路线图](https://res.cloudinary.com/incoder/image/upload/v1528189150/blog/gitpages-zjj-tms.png.jpg) + +## 住宿 + +现在还未确定路线,个人推荐路线二;其次,16,17,18号需要住宿,要提前预定旅店 + +## 美食 + +### 胡师傅三下锅 + +三下锅,所谓的三下锅其实就是一种很方便的干锅,它是由三种主料做成的,炖着不放汤的火锅,三角坪附近的那个“胡师傅三下锅”味道不错,三下锅50元一份,分量很够吃的,包你吃够吃好!推荐的就是干煸肠子,干煸核桃肉和湘西腊肉三种混在一起炖,吃的同时还可以点一份酸萝卜,又脆又酸。真的是极品哦!(吃过后,发现并没有网上说的这么好吃,就是大烩菜,哈哈哈) + +等等。。。 + +## 汇总 + +![汇总](https://res.cloudinary.com/incoder/image/upload/v1527434516/blog/gitpages-zjj-summary.png) + +## 游记 + +废话不说,**武陵源景区**不用去,虽说是5A景区,除了山还是山,而且商业气息很重,很多地方都不能步行,需要坐缆车,电梯等交通工具,况且这次去森林公园那边在修路,说是在修高铁,建议直接去 **大峡谷风景区** 和 **天门山景区** + +### 武陵源景区 + +整个武陵 +![武陵源](https://res.cloudinary.com/incoder/image/upload/v1530682536/blog/travel/travel-zjj-wly.jpg) + +### 大峡谷风景区 + +![大峡谷](https://res.cloudinary.com/incoder/image/upload/v1530682775/blog/travel/travel-zjj-dxg.jpg) + +### 天门山景区 + + \ No newline at end of file diff --git a/source/_posts/treasure.md b/source/_posts/treasure.md new file mode 100644 index 000000000..fd8acca83 --- /dev/null +++ b/source/_posts/treasure.md @@ -0,0 +1,216 @@ +--- +title: 藏经阁 +date: 2018-07-16 23:00:10 +categories: Resources +tag: DevTool +--- + +{% cq %}工欲善其事,必先利其器{% endcq %} + +记录汇总一些资源库 + + + +{% tabs Tags %} + + +**快速满足你所需各种资源汇总** +[Hi World](http://www.shandowsocks.info) +[创造师导航](http://chuangzaoshi.com) +[UI设计师导航](http://so.uigreat.com) +[Devdocs](https://devdocs.io) + + + +**DevTools** + +{% subtabs devtool %} + +**集齐宇宙IDE,可以召唤神龙::>_<::** +[Jetbrains 全家桶](http://www.jetbrains.com) +[Android Studio](https://developer.android.google.cn/studio) +[Xcode](https://developer.apple.com/xcode/ide) +[Eclipse](http://www.eclipse.org/downloads/eclipse-packages) +[Visual Studio Code](https://code.visualstudio.com) +[Sublime](http://www.sublimetext.com) +[PostMan](https://www.getpostman.com/apps) +[Xshell Xftp](http://www.netsarang.com/products/xsh_overview.html) + + + +**必备的一些辅助插件,让你效率翻倍** + +## 通用 + +1. [Alibaba Java Coding Guidelines](https://github.com/alibaba/p3c):阿里巴巴 Java 代码规范 +2. [GrepConsole](https://github.com/krasa/GrepConsole):控制台日志自定义 +3. [GsonFormat](https://github.com/zzz40500/GsonFormat):json 生成对应的实体 bean +4. [GenerateAllSetter](https://github.com/gejun123456/intellij-generateAllSetMethod):对象中所有属性生成 set 方法 +5. [ignore](http://ignore.hsz.mobi):文件忽略 +6. [Lombok](https://github.com/mplushnikov/lombok-intellij-plugin):简化实体 bean +7. [Markdown Navigator](https://github.com/vsch/idea-multimarkdown):Markdown 文件支持 +8. [PlantUML integration](https://github.com/esteinberg/plantuml4idea):UML 文件支持 +9. [Translation](http://yiiguxing.github.io/TranslationPlugin):语言翻译 + + +## Android + +1. [Android ButterKnife Zelezny](https://github.com/avast/android-butterknife-zelezny):ButterKnife 相关支持 +2. [Android Material Design Icon Generator](https://github.com/konifar/android-material-design-icon-generator-plugin):Material icon 生成 +3. [Android Parcelable code generator](https://github.com/mcharmas/android-parcelable-intellij-plugin):Parcelable 代码生成 +4. [Android Postfix Completion](https://plugins.jetbrains.com/plugin/7775-android-postfix-completion):.toast,.log 等快捷写法 +5. [Android Resource Usage Count](https://github.com/niorgai/Android-Resource-Usage-Count):资源使用统计 +6. [Android Studio Prettify](https://github.com/Haehnchen/idea-android-studio-plugin):模板代码生成,比如:findViewById +7. [ADB Idea](http://www.developerphil.com/renaming-your-gradle-build-files):ADB 相关命令 +8. [EventBus3 Intellij Plugin](https://plugins.jetbrains.com/plugin/8603-eventbus3-intellij-plugin):EventBus 相关支持 +9. [LayoutFormatter](https://github.com/drakeet/LayoutFormatter):格式化 XML 布局 +10. [SQLScout](https://plugins.jetbrains.com/plugin/8322-sqlscout-sqlite-support-):SQL 相关支持 + +## Java + +1. [Alibaba Cloud Toolkit](https://www.aliyun.com/product/cloudtoolkit):服务部署工具 +2. [JsonToKotlinClass](https://github.com/wuseal/JsonToKotlinClass):json 生成 kotlin 对象 +3. [MyBatisCodeHelperPro](https://github.com/gejun123456/MyBatisCodeHelper-Pro):mybatis 支持及 XML 代码调试 + + + +**常用的代码托管平台,设备可以坏,代码不能丢** +[Github](https://www.github.com) +[Gitlab](https://www.gitlab.com) +[Gitee](https://www.gitee.com) +[Coding](https://coding.net) + + + +**装起逼来,我自己都怕** + +## 根据文字生成字符画 + +1. [taag](http://patorjk.com/software/taag) +2. [ascii](http://www.network-science.de/ascii/) + +## 根据图片生成字符画 + +1. [ConvertPhoto2Char](http://life.chacuo.net/convertphoto2char) +2. [Img2Txt](https://www.degraeve.com/img2txt.php) + +## 代码生成图片 + +1. [Carbon](https://carbon.now.sh) + +## 云端应用 + +1. [Uzer](https://uzer.me/) + +## 其他 + +1. [emojicopy](https://www.emojicopy.com/):表情 +2. [Processon](https://www.processon.com/):在线编辑流程图 +3. [Pickfrom](https://zh.pickfrom.net/):一站式工具平台 + + + +{% endsubtabs %} + + + +**开发者常用的官方网站** + +{% subtabs website %} + +**一线大厂开发者网站** +[Google Developer](https://developers.google.com) +[Apple Developer](https://developer.apple.com) +[Microsoft Developer](https://developer.microsoft.com) +[Facebook Developer](https://developers.facebook.com) +[Twitter Developer](https://developer.twitter.com) +[Github Developer](https://developers.github.com) +~~[Baidu Developer](https://developer.baidu.com)~~ +[Alibaba Developer](https://dev.aliyun.com) +[Tencent Developer](http://open.qq.com) + + + +**一线大厂团队博客** +[IBM 技术社区](https://www.ibm.com/developerworks/cn/) +[Netflix](https://medium.com/netflix-techblog) +[Techie Delight](https://www.techiedelight.com) +[Linkedin 技术博客](https://engineering.linkedin.com/blog) +[Dropbox 技术博客](https://blogs.dropbox.com/tech) +[Facebook 技术博客](https://code.fb.com) +[淘宝中间件团队](http://jm.taobao.org) +[美团技术博客](https://tech.meituan.com) +[360技术博客](http://blogs.360.cn) +[有赞技术团队](https://tech.youzan.com) + + + +**手机厂商** +[Android Developer](https://developer.android.com) +[iOS Developer](https://developer.apple.com/ios) +[Samsung Developer](https://developer.samsung.com) +[Huawei Developer](http://developer.huawei.com/cn) +[XiaoMi Developer](https://dev.mi.com) +[HTC Developer](https://www.htcdev.com/) +[Flyme Developer](https://open.flyme.cn/) +[Oppo Developer](https://open.oppomobile.com) +[Vivo Developer](https://dev.vivo.com.cn) +[360 Developer](http://dev.360.cn) +~~[Smartisan Developer](http://dev.smartisan.com)~~ + + + +**应用市场** +[Google Play](https://play.google.com) +[App Store](https://play.google.com) +[XiaoMi 应用市场](http://app.mi.com) +[Huawei 应用市场](http://developer.huawei.com/consumer/cn/devunion/ui/server/appMarket.html) +[360 应用市场](http://zhushou.360.cn) +[酷安市场](https://www.coolapk.com) +[应用宝](http://sj.qq.com) + + + +**小程序** +[PWA](https://developers.google.cn/web/progressive-web-apps) +[微信小程序](https://mp.weixin.qq.com/cgi-bin/wx) +[支付宝小程序](https://docs.alipay.com/mini/developer/getting-started) +[快应用](https://www.quickapp.cn) + + + +**要折腾,来呀~** +[LineageOS](https://lineageos.org) +[XDA](https://forum.xda-developers.com) +[Mokee](https://www.mokeedev.com) +[MoDaCo](https://www.modaco.com) +[机锋](http://bbs.gfan.com/forum.php) +[智友](http://bbs.zhiyoo.com) +[MiUi](http://rom.xiaomi.cn) +[0pengApps](https://opengapps.org) + + +{% endsubtabs %} + + + +**国内外云厂商** +[Google Cloud](https://console.cloud.google.com) +[Microsoft Azure](https://azure.microsoft.com) +[Amazon Web Services](https://aws.amazon.com) +[Aliyun](https://www.aliyun.com) +[Tencent Cloud](https://cloud.tencent.com) +[DiDi Cloud](https://www.didiyun.com) +[MT Cloud](https://www.mtyun.com) +[NetEase Cloud](https://www.163yun.com) + + + +**常用镜像** +[Tsinghua](https://mirrors.tuna.tsinghua.edu.cn) +[LUG](http://mirrors.ustc.edu.cn/) +[Gradle](http://services.gradle.org/distributions) +[Firfox](https://download-installer.cdn.mozilla.net/pub/firefox/releases) + + +{% endtabs %} \ No newline at end of file diff --git a/source/_posts/wechat-mini1.md b/source/_posts/wechat-mini1.md new file mode 100644 index 000000000..4f30e3c29 --- /dev/null +++ b/source/_posts/wechat-mini1.md @@ -0,0 +1,121 @@ +--- +title: 微信小程序之 Vant实战(一) +date: 2021-02-12 11:11:11 +categories: Wechat +tag: [Wechat, Vant] +--- + +过年正好时间比较集中,可以把之前的一个想法付诸实践,之前一直想给老爸做一个类似于账单管理的应用,方便他每天把客户需要物品记录成一个清单进行管理,其中主要包含已下功能点。其一,支持添加任务列表(账单);其二,支持任务列表分享(账单)。以上是我的第一期规划功能规划,话不多说我们就一起来跟着我来完成这个小程序的开发吧,本篇主要讲小程序的初始化相关工作 + + + +这是我第一次来开发小程序,虽然之前有开发 Android 客户端的经验,有一定的客户端经验,但是小程序却一直没有去实践,我主要是觉得小程序的使用体验真的很差。但随着现在人们的硬件设备越来越好,并且微信团队在应用底层也做了很多的扩展和优化,现在使用小程序开发轻量级的应用还是很方便且高效 + +## 环境及选型 + +1. OS:macOS(11.2.1) +2. [IDE:WeChat Devtools(1.05.2102010)](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html) +3. Node:v15.5.0 +4. [Vant:1.6.7](https://vant-contrib.gitee.io/vant-weapp/#/intro) +5. [微信小程序账号](https://mp.weixin.qq.com) + +>关于账号的申请,这里不做讲解,请自行解决 + +## 初始化项目 + +### 创建项目 + +不废话,直接看图 + +![](https://res.cloudinary.com/incoder/image/upload/v1613191822/blog/wechat-create.png) + +### 项目结构 + +项目是一个基于云开发的方式,创建完成后会包含云相关的一些操作实例 + +```text +bill + ├── cloudfunctions/ # 云函数管理【清空当前文件夹下的内容】 + │ │── callback/ + │ │── echo/ + │ │── login/ + │ └── openapi/ + ├── minprogram/ + │ │── components/ # 组件【清空当前文件夹下内容】 + │ │── images/ # 图片管理【清空当前文件夹下内容】 + │ │── pages/ # 页面管理【除 index 页面,其余都删除】 + │ │ │── addFunction/ + │ │ │── chooseLib/ + │ │ │── databaseGuide/ + │ │ │── deployFunctions/ + │ │ │── im/ + │ │ │── index/ + │ │ │ │── index.js + │ │ │ │── index.json + │ │ │ │── index.wxml + │ │ │ │── index.wxss + │ │ │ └── user-unlogin.png + │ │ │── openapi/ + │ │ │── storageConsole/ + │ │ └── userConsole/ + │ │── style/ # 样式管理 + │ │── app.js # 项目入口逻辑管理 + │ │── app.json # 组件库配置 + │ │── app.wxss # 全局样式设置 + │ └── sitemap.json # + ├── project.config.json # 项目配置文件 + └── README.md # 项目说明 +``` + +### 精简项目 + +从上面我们可知初始化的项目,包含了一些示例,我们对其精简 + +1. 清空 cloudfunctions 目录下的云函数内容 +2. 根据项目结构里的备注,进行删除相关的文件 + * 清空 components 文件夹下的组件 + * 清空 images 文件夹中的内容 + * 删除 pages 文件夹下,**除** index 的文件夹 + * 删除 index 文件夹下的 user-unlogin.png 文件 +3. 修改文件内容 + * 清空 index 文件夹下 index.wxml,index.wxss 文件中的内容 + * 修改 index 文件夹下 index.js 文件内容 + * 清空 minprogram 文件夹下 app.wxss 文件中的样式内容 +4. 修改配置 + * 移除 app.json 文件中 已经移除掉的 pages 的配置 + * 修改 project.config.json 文件,移除 miniprogram 的配置 + +### 添加 vant 组件 + +整个步骤,如下截图 +![](https://res.cloudinary.com/incoder/image/upload/v1613403600/blog/wechat-init.png) + +> 执行命令根据官方提供的方式和自身喜好选择,我这里使用的是 `yarn` 命令进行安装相关的依赖 + +### 测验效果 + +这里以添加 Button 为例来查看是否生效 + +1. 在 pages/index 路径下的 `index.json` 文件中,添加 vant 的 Button 组件 + ```json + { + "usingComponents": { + "van-button": "@vant/weapp/button/index" + } + } + ``` +2. 在 pages/index 路径下的 `index.wxml` 文件中,添加 vant 的相关组件 + ```xml + 主要按钮 + 信息按钮 + 警告按钮 + 危险按钮 + ``` +3. 编译,在模拟器中查看效果 + +>对于引用的组件,是公共的,可以写在 `app.json` 文件中 + +## 参考 + +1. [官方开发文档](https://developers.weixin.qq.com/miniprogram/dev/framework) +2. [微信小程序组件库Vant weapp的使用与weui零基础入门课程](https://www.bilibili.com/video/BV18V411C7VV) \ No newline at end of file diff --git a/source/_posts/windows-devtool.md b/source/_posts/windows-devtool.md new file mode 100644 index 000000000..ed849085a --- /dev/null +++ b/source/_posts/windows-devtool.md @@ -0,0 +1,203 @@ +--- +title: Windows 之 常用应用安装 +date: 2019-09-25 21:08:10 +categories: Windows +tag: DevTool +--- + +这是一篇在Windows系统下,持续更新常用开发软件安装汇总,当然一些简单得安装就在这里记录,不废话了 + +## JDK + +[官方下载地址](https://www.oracle.com/technetwork/java/javase/downloads/index.html),选择需要的版本下载安装包 + +安装完成,设置环境变量,右击`我的电脑`-->`属性`-->`高级系统设置`-->`高级`-->`环境变量` + + + +1. 在系统变量里新建 **`JAVA_HOME`** 变量,变量值为你的JDK的安装路径,比如:C:\Program Files\Java\jdk1.8.0_60 +2. 在系统变量里新建 **`CLASSPATH`** 变量 + ```bash + .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar + ``` +3. 找到 **`path`** 变量(已存在不用新建)添加变量值 + ```bash + %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin + ``` +4. 保存修改,并启动 CMD 进行验证,输出安装的 Java 版本表示安装成功 + ```bash + java -version + ``` +>变量值之间用 **`;`** 隔开。注意原来Path的变量值末尾有没有 **`;`** 号,如果没有,先输入 **`;`** 号再输入 + +## Git + +* [官方下载地址](https://git-scm.com/downloads) +* [清华镜像下载地址](https://mirrors.tuna.tsinghua.edu.cn/github-release/git-for-windows/git/) + +关于 Git 的安装没有什么可说的(使用默认配置即可),基本上就是下一步,下一步,到完成 + +## MySQL + +* [官方下载地址](https://dev.mysql.com/downloads/) +* 下载`MySQL Community Server`或者`MySQL Installer for Windows`都可以,这里我下载的是`MySQL Community Server` + ![](https://res.cloudinary.com/incoder/image/upload/v1569640711/blog/windows-mysql-download.png) + +安装步骤请看截图所示 +![windows-mysql-install](https://res.cloudinary.com/incoder/image/upload/v1569640478/blog/windows-mysql-install.png) + +## Tomcat + +[官方网站](http://tomcat.apache.org/),选择需要的版本下载 + +* 官方 Tomcat 镜像:https://downloads.apache.org/tomcat/ +* 北京理工大学:https://mirrors.bit.edu.cn/apache/tomcat/ +* 北京外国语大学:https://mirrors.bfsu.edu.cn/apache/tomcat/ +* 清华大学:https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/ + +### 安装 + +下载对应版本的文件,这里比如 `apache-tomcat-9.0.26` 版本文件,放入你系统某个位置,最好是英文路径且路径中没有空格或特殊字符,并解压文件 + +### 配置 + +Tomcat 和 JDK 一样为了方便使用都需要配置环境变量,右击`我的电脑`-->`属性`-->`高级系统设置`-->`高级`-->`环境变量` +1. 在系统变量里新建 **`CATALINA_BASE`** 变量,变量值为你的Tomcat路径,比如:D:\DevTools\apache-tomcat-9.0.26 +2. 在系统变量里新建 **`CATALINA_HOME`** 变量,变量值为你的Tomcat路径,比如:D:\DevTools\apache-tomcat-9.0.26 +3. 找到 **`path`** 变量(已存在不用新建)添加变量值 + ```bash + ;%CATALINA_HOME%\lib;%CATALINA_HOME%\bin; + ``` + >变量值之间用 **`;`** 隔开。注意原来Path的变量值末尾有没有 **`;`** 号,如果没有,先输入 **`;`** 号再输入 +4. 保存修改,并启动 CMD 进行验证 + ```bash + statrup + ``` + +#### 乱码 + +Tomcat 控制台中中文乱码,需要修改 Tomcat 配置文件 **`logging.properties`** 中的字符编码,将默认的 **UTF-8** 改为 **GBK**,文件路径`\conf` +![windows-tomcat-encode](https://res.cloudinary.com/incoder/image/upload/v1569644342/blog/windows-tomcat-encode.png) + +#### 缓存 + +文件路径`\conf`,修改 **`context.xml`** 文件,在 `` 标签中添加如下配置即可 +```xml + +``` + +## Maven + +[官方网站](http://maven.apache.org) + +* 官方 Maven 镜像:https://downloads.apache.org/maven/ +* 北京理工大学:https://mirrors.bit.edu.cn/apache/maven/ +* 北京外国语大学:https://mirrors.bfsu.edu.cn/apache/maven/ +* 清华大学:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/ + +### 安装 + +下载对应版本的文件,这里比如 `apache-maven-3.6.3` 版本文件,放入你系统某个位置,最好是英文路径且路径中没有空格或特殊字符,并解压文件 + +### 配置 + +Maven 和 JDK 一样为了方便使用都需要配置环境变量,右击`我的电脑`-->`属性`-->`高级系统设置`-->`高级`-->`环境变量` +1. 在系统变量里新建 **`MAVEN_HOME`** 变量,变量值为你的Tomcat路径,比如:D:\DevTools\apache-maven-3.6.3 +2. 找到 **`path`** 变量(已存在不用新建)添加变量值 + ```bash + ;%MAVEN_HOME%\bin; + ``` + >变量值之间用 **`;`** 隔开。注意原来Path的变量值末尾有没有 **`;`** 号,如果没有,先输入 **`;`** 号再输入 +3. 保存修改,并启动 CMD 进行验证 + ```bash + mvn -version + ``` + +### 镜像地址(可选) + +如果你服务所依赖的包都是使用公司内部的私服,或者需要加快依赖的同步速度,那么建议你修改 maven 同步镜像的配置,编辑 `\conf` 路径下 `settings.xml` 文件,在 标签下修改镜像地址 + +```xml + + + aliyunmaven + * + 阿里云central仓库 + https://maven.aliyun.com/repository/central + + + aliyunmaven + * + 阿里云jcenter-public仓库 + https://maven.aliyun.com/repository/public + + + aliyunmaven + * + 阿里云google仓库 + https://maven.aliyun.com/repository/google + + + aliyunmaven + * + 阿里云gradle-plugin仓库 + https://maven.aliyun.com/repository/gradle-plugin + + + aliyunmaven + * + 阿里云spring仓库 + https://maven.aliyun.com/repository/spring + + + aliyunmaven + * + 阿里云spring-plugin插件仓库 + https://maven.aliyun.com/repository/spring-plugin + + + aliyunmaven + * + 阿里云grails-core插件仓库 + https://maven.aliyun.com/repository/grails-core + + + aliyunmaven + * + 阿里云apache-snapshots仓库 + https://maven.aliyun.com/repository/apache-snapshots + + +``` + +### 本地依赖存放地址 + +默认,maven 的依赖都存放在 `C:Users//.m2/reposittory` 路径下,你可以指定 maven 同步的依赖存默认放在你设置的路径下,编辑 `\conf` 路径下 `settings.xml` 文件,修改 标签的内容 + +```xml +D:DevTools/maven/repository +``` + +### 配置修改验证 + +配置完成,在命令行输入 `mvn help:system` 测试,查看下载链接里面是否是现在配置的镜像地址,以及下载后的文件是否存放在自定设置的目标地址 + +## CMD + +### 常用命令 + +1. IP地址 + ```bash + ipconfig + ``` +2. 查看端口 + ```bash + # 查看端口号:netstat -ano | findstr 端口号 + netstat -ano | findstr 8080 + # 查看占用端口号进程 :tasklist | findstr 进程号 + tasklist | findstr 12836 + # kill 指定进程:taskkill -PID 进程号 -F + taskkill -PID 12836 -F + ``` + 截图如下: + ![windows-task](https://res.cloudinary.com/incoder/image/upload/v1569655372/blog/windows-task.png) diff --git a/source/_posts/zxing1.md b/source/_posts/zxing1.md new file mode 100644 index 000000000..8d95cd86d --- /dev/null +++ b/source/_posts/zxing1.md @@ -0,0 +1,106 @@ +--- +title: Zxing(一)二维码基础知识 +date: 2019-05-01 09:01:22 +categories: Android +tag: [Zxing] +--- + +移动端开发,一个避不开的老生常谈功能开发,二维码扫描识别(主要)及二维码生成(辅助),虽然已有现成的开源项目提供了功能,仅仅作为功能的开发集成和调试,其实远远不够,应该在完成功能开发的基础上去学习背后的技术点和原理,让我们更加完整的掌握该技术。废话不多说,本篇是 Zxing 相关技术的第一篇文章,本篇不会涉及到应用相关,仅仅是二维码基础知识的学习记录。 + + + +## QRcode + +QRcode(全称:Quick Response Code,快速响应矩阵图码) +* 1994 年由[日本 DENSO WAVE](https://zh.wikipedia.org/wiki/%E9%9B%BB%E8%A3%9D)公司发明, +* QR 码使用`四种`标准化编码模式(数字,字母数字,字节(二进制)和汉字)来存储数据 +* QR 码可以存储更多信息,可在小空间内打印,可以从 360°任一方向读取,可以对变脏和破损的图码有一定的容错能力,并且可以有效处理各种数据,支持数据合并等 +* QR 码的种类:[QR 码(模型1,模型2)](https://www.qrcode.com/zh/codes/model12.html);[Micro QR 码](https://www.qrcode.com/zh/codes/microqr.html);[iQR 码](https://www.qrcode.com/zh/codes/iqr.html);[SQRC](https://www.qrcode.com/en/codes/sqrc.html);[Frame QR](https://www.qrcode.com/en/codes/frameqr.html) + +>QR 码(模型1,模型2): +>模型1:最早制作的 QR 码。最高版本为 14(73x73 码元),最多可以处理 1167 位数字 +>模型2:是模型1的改良版,最高版本为 40(177x177 码元),最多可以处理 7089 位数字,现在我们通常所说的 QR 码一般指模型2 +>Micro QR 码:该码只有 1 个定位图案,可以在更小的空间内打印,最高版本为 M4(17x17 码元),最多可以因 35 位数字 +>iQR码:可生成正方形或长方形,可以支持内外翻转,黑白反色,圆点图案(直接打标在部件上)。理论上的最高版本为 61(422x422 码元),最多大约可以处理 4万位数字 +>SQRC:安全快速响应代码(Secure Quick Response code,简写SQRC)是一种QR代码,在终结符之后包含“私有数据”段而不是指定的填充字节“ec 11”。必须使用加密密钥对此专用数据段进行解密。这可用于存储私人信息和管理公司的内部信息。 +>Frame QR:FrameQR是具有“画布区域”的QR码,可以灵活使用。在这个代码的中心是画布区域,其中可以灵活地安排图形,字母等,使得可以布置代码而不会丢失插图,照片等的设计 + +## 标准及发展 + +* 1997年10月:AIM(自动识别和流动协会)国际 +* 1999年1月:JIS X 0510 +* 2000年6月 + * ISO / IEC 18004:2000信息技术 - 自动识别和数据捕获技术 - 条形码符号 - QR码(现已撤销) + * 定义QR码模型1和2符号 +* 2006年9月1日: + * ISO / IEC 18004:2006信息技术 - 自动识别和数据捕获技术 - QR码2005条形码符号规范(现已撤销) + * 定义QR码2005符号,QR码模型2的扩展。不指定如何读取QR码模型1符号,或要求符合性。 +* 2015年2月1日: + * ISO / IEC 18004:2015信息 - 自动识别和数据捕获技术 - QR码条形码符号规范 + * 将QR Code 2005符号重命名为QR Code,并对某些程序和次要更正添加说明 + +## 结构 + +![qrcode-structure](https://res.cloudinary.com/incoder/image/upload/v1556733911/blog/QR_Code_Structure_Example.png) +>图片来自[维基百科](https://zh.wikipedia.org/wiki/QR%E7%A2%BC),Version7 + +如图所示,QR码由 5 部分组成 +1. 版本信息:记录具体的版本信息(仅存在 Version7 以上) + * version1 是 21x21 的矩阵 + * 最高 version40 是 177x177 的矩阵 + * 计算公式:(V-1)*4+21,V 代表版本号 +2. 格式信息:记录使用的`掩码`和`纠错等级` +3. 数据及容错密钥 +4. 数据需求模块 +5. 静态区域 + +### IEC 18004 + +![qr-iec-18004](https://res.cloudinary.com/incoder/image/upload/v1556802979/blog/qr-iec-18004.png) +IEC 18004标准中给出了详细的说明 +* 位置探测图形、位置探测图形分隔符、定位图形:用于对二维码的定位,对每个QR码来说,位置都是固定存在的,只是大小规格会有所差异; +* 校正图形:规格确定,校正图形的数量和位置也就确定了; +* 格式信息:表示改二维码的纠错级别,分为L、M、Q、H; +* 版本信息:即二维码的规格,QR码符号共有40种规格的矩阵(一般为黑白色),从21x21(版本1),到177x177(版本40),每一版本符号比前一版本 每边增加4个模块。 +* 数据和纠错码字:实际保存的二维码信息,和纠错码字(用于修正二维码损坏带来的错误) + +### 掩码 + +掩码的作用 +1. 为了对数据区域进行掩模以利于扫描仪识别,可以避免数据区域出现连续的空白或连续的黑色区, +2. 避免了数据区出现类似定位点样式的正方形出现。掩模图案在整个数据区域的网格内不断重复进行掩模计算(功能图形不进行掩模),数据区上对应掩模黑色模块的单元将会反转。 +3. 每个二维码上会有两组相同的格式信息出现,并且带有 BCH 纠错 + +> 在计算机科学中,掩码就是一个二进制串,通过和数据进行`异或运算`来变换数据。在 QR 码中,掩码也是通过异或运算来变换数据矩阵,所以 QR 码掩码就是预先定义好的矩阵 + +### 纠错等级 + +相对而言,容错率愈高,QR 码图形面积愈大,所以一般折中使用 15% 容错能力 + +| 错误修正容量 | +| --- | --- | +| L 等级 | 7%的字码可被修正 | +| M 等级 | 15%的字码可被修正 | +| Q 等级 | 25%的字码可被修正 | +| H 等级 | 30%的字码可被修正 | + +### 编码 QR 码步骤 + +1. 数据分析(data analysis):分析输入数据,根据数据决定要使用的 QR 码版本、容错等级和编码模式 +2. 编码数据(data encoding):根据选择的编码模式,将输入的字符串转变成比特流,插入模式标识码(mode indicator)和终止标识符(terminator),将比特流切分成 8 比特的字节,加入填充字节来满足标准的数据字码数要求 +3. 计算容错码(error correction coding):对步骤二产生的比特流计算容错码,附在比特流之后。高版本的编码方式可能需要将数据流切分成块再分别进行容错码计算 +4. 组织数据(structure final message):根据结构图把步骤三得到的有容错的数据切分,准备填充 +5. 填充(module placement in matrix):把数据和功能性图样根据标准填充到矩阵中 +6. 应用数据掩码(data masking):应用标准中的 8 个数据掩码来变换编码区域的数据,选择最优的掩码应用 +7. 填充格式和版本信息(format and version information):计算格式和版本信息填入矩阵,完成 QR 码 + +## 附录 + +* [维基百科·QR码·中文](https://zh.wikipedia.org/wiki/QR%E7%A2%BC) +* [维基百科·QR码·英文·推荐](https://en.wikipedia.org/wiki/QR_code) +* [二维码(QR code)基本结构及生成原理](https://blog.csdn.net/u012611878/article/details/53167009) +* [QRcode](https://www.qrcode.com) +* [二维码的生成细节和原理](https://coolshell.cn/articles/10590.html) +* [为程序员写的Reed-Solomon码解释](https://www.jianshu.com/p/8208aad537bb) +* [ISO/IEC 18004:2015·PDF](https://coolshell.cn/articles/10590.html) +* [ISO/IEC 18004:2015·PDF](https://coolshell.cn/articles/10590.html) \ No newline at end of file diff --git a/source/_posts/zxing2.md b/source/_posts/zxing2.md new file mode 100644 index 000000000..047d0d42f --- /dev/null +++ b/source/_posts/zxing2.md @@ -0,0 +1,139 @@ +--- +title: Zxing(二)Android 模块应用源码探索 +date: 2019-05-02 22:41:10 +categories: Android +tag: [Zxing] +--- + +[ZXing(“Zebra Crossing”](https://github.com/zxing/zxing))用于Java,Android的条形码扫描库。虽然当前开源库仅处于维护模式,意味着更改是由贡献的补丁来驱动,只会考虑错误修复和次要的增强功能 + +本篇开启 [ZXing项目Android](https://github.com/zxing/zxing/tree/master/android) 模块的探索学习之路,那么首先我们要集成该模块到项目中 + + + +## 模块集成 + +* 下载官方项目[Zxing](https://github.com/zxing/zxing) +* 使用 AS 创建一个新的 Project + +>编译环境 +* Android studio:3.4 +* gradle:5.1.1 +* SDK:28 +* JDK:1.8 + +演示项目[rc-android-zxing](https://github.com/RootCluster/rc-android-zxing) + +### 导入步骤 + +* 导入 module + ![import_module](https://raw.githubusercontent.com/RootCluster/rc-android-zxing/zxing/images/import_module.png) +* 选择 module + ![select_import_module](https://github.com/RootCluster/rc-android-zxing/raw/zxing/images/select_import_module.png) +* 移除最小及目标版本设置 + ![remove_min_target_version](https://github.com/RootCluster/rc-android-zxing/raw/zxing/images/remove_min_target_version.png) +* 添加项目核心依赖 + ![import_dependencies](https://github.com/RootCluster/rc-android-zxing/raw/zxing/images/import_dependencies.png) +`com.google.zxing:android-core:3.3.0`:实质是[android-core](https://github.com/zxing/zxing/tree/master/android-core)模块 +`com.google.zxing:core:3.3.3`:实质是[core](https://github.com/zxing/zxing/tree/master/core)模块 +* 删除`app`module(可选) + +### 项目展示 + +![zxing](https://github.com/RootCluster/rc-android-zxing/raw/zxing/images/zxing.gif) + +当然你也可以下载官方提供的应用[google play](https://play.google.com/store/apps/details?id=com.google.zxing.client.android) + +### 异常问题处理 + +#### 相机出现问题 + +##### 表现 + +如果你运行的设备是 Android 6.0 以上版本,那么在启动应用程序后,应该会提示你“很遗憾,Android 相机出现问题,你可能需要重启设备”,如下图 +![project_problem](https://github.com/RootCluster/rc-android-zxing/raw/zxing/images/project_problem.png) + +##### 分析 + +分析运行日志,进行定位`CaptureActivity.java`类,在初始化相机时,由于没有相机权限,因此无法正常运行应用 +![zxing_error_log](https://github.com/RootCluster/rc-android-zxing/raw/zxing/images/zxing_error_log.png) + +##### 解决方式 + +```java +// CaptureActivity.jaca +// line 266 && line 443 +mHolder = surfaceHolder; +if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { + if (ContextCompat.checkSelfPermission(CaptureActivity.this, + android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + // 先判断有没有权限 ,没有就在这里进行权限的申请 + ActivityCompat.requestPermissions(CaptureActivity.this, + new String[]{Manifest.permission.CAMERA}, CAMERA_OK); + } else { + // 说明已经获取到摄像头权限了 + initCamera(surfaceHolder); + } + } else { + initCamera(surfaceHolder); + } + +// line 803 +@Override +public void onRequestPermissionsResult(int requestCode + , @NonNull String[] permissions, @NonNull int[] grantResults) { + // If request is cancelled, the result arrays are empty. + if (requestCode == CAMERA_OK) { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted, yay! Do the + // contacts-related task you need to do. + initCamera(mHolder); + } + } +} +``` +#### 其他问题 + +同样我们在`EncodeActivity.java`文件中加入读取内存卡的权限`READ_EXTERNAL_STORAGE` + +## 项目分析 + +### 项目概要 + +``` +rc-android-zxing + ├── book/ + ├── camera/ + ├── clipboard/ + ├── encode/ + ├── history/ + ├── result/ + ├── share/ + ├── wifi/ + ├── AmbientLightManager + ├── BeepManager + ├── CaptureActivity + ├── CaptureActivityHandler + ├── Contents + ├── DecodeFormatManager + ├── DecodeHandler + ├── DecodeHintManager + ├── DecodeThread + ├── FinishListener + ├── HelpHelper + ├── InactivityTimer + ├── Intents + ├── IntentSource + ├── LocaleManager + ├── PreferencesActivity + ├── PreferencesFragment + ├── ScanFromWebPageManager + ├── ViewfinderResultPointCallback + └── ViewfinderView + +``` + +## 项目源码 + +## 总结 \ No newline at end of file diff --git a/source/about/index.md b/source/about/index.md new file mode 100644 index 000000000..ee51e4e88 --- /dev/null +++ b/source/about/index.md @@ -0,0 +1,69 @@ +--- +title: 关于 +date: 2018-05-04 17:50:34 +--- + +{% centerquote %} + +Life's A Struggle! +生命不息,折腾不止,当然这不能**瞎**折腾 + +{% endcenterquote %} + +{% note danger %} + +~~也不想声讨,宋岳庭原唱单曲《[Life's A Struggle](https://music.163.com/#/song?id=145223)》在国内,遭到全网下架!!!~~ +🥰 网易云于 2024.04.05 重新上架 🥳 +😭 但由于版权保护,无法外链播放 😂 + +{% endnote %} + + + +## 关于我 + +坐标杭州,理工男,机械制造与自动化专业 +14 年毕业后,误打误撞闯进计算机的世界 +16 年开始自学 Android 开发,后自学 Java 服务端相关开发,现从事服务端开发工作 + +## 关于本站 + +{% note success %} + +本站基于 [Hexo](https://hexo.io) 和 [NexT](https://theme-next.js.org) 搭建,使用 [Git-Pages](https://pages.github.com) 托管服务 + +{% endnote %} + +该网站从 2018 年开始了我的记录之旅,主要用来 ~~记录学习~~ 分享生活,和一些归档资源的整理 + +- 资源:便捷,高效,有趣的工具资源 +- 经验:工作中一些问题解决方式及思考 +- 阅读:阅读一些书籍的收获以及感兴趣的文字 +- 学习:编程语言,热门开源框架的学习过程记录 +- 影音:视觉,听觉的享受分享 + +## 不止一面 + +{% note primary %} + +优雅永不过时,相信文字的力量 + +{% endnote %} + +{% linkgrid %} + +Backend | https://backend.incoder.org | 服务端技能栈 | https://res.cloudinary.com/incoder/image/upload/v1709221737/incoderapp/app/black-backend.png +Mobile | https://mobile.incoder.org | 移动端技能栈 | https://res.cloudinary.com/incoder/image/upload/v1709221861/incoderapp/app/black-mobile.png +RootCluster | https://rootcluster.github.io | 学习,练习 | https://res.cloudinary.com/incoder/image/upload/v1709222094/incoderapp/app/black-rootcluster.png +MuseFlow | https://museflow.io | 业余爱好 | https://res.cloudinary.com/incoder/image/upload/v1709221936/incoderapp/app/black-museflow.png +IncoderApp | https://incoder.app | 个人应用 | https://res.cloudinary.com/incoder/image/upload/v1709221686/incoderapp/app/black-incodeapp.png +OpenCWMP | https://cwmp.incoder.app | 广域网协议 | https://res.cloudinary.com/incoder/image/upload/v1709220476/incoderapp/app/black-hub.png + +{% endlinkgrid %} + +## 重要变更 + +### 2024-06-24 + +1. 博客正式更名为《星海》,因为我的征程就是星辰大海 +2. 博客变更主题 NexT Scheme 为 Mist diff --git a/source/categories/index.md b/source/categories/index.md new file mode 100644 index 000000000..dea1cbcc2 --- /dev/null +++ b/source/categories/index.md @@ -0,0 +1,6 @@ +--- +title: 分类 +type: categories +date: 2018-03-24 22:38:58 +comments: false +--- diff --git a/source/links/index.md b/source/links/index.md new file mode 100644 index 000000000..3105a4338 --- /dev/null +++ b/source/links/index.md @@ -0,0 +1,25 @@ +--- +title: 友链 +type: links +date: 2024-01-24 22:38:58 +comments: false +--- + +{% note success %} +排名不分先后顺序 +{% endnote %} + +## 博客 + +{% linkgrid %} +南贺神社 | https://ceaser.wang | 空谈误国,实干兴邦
你写程序有写诗一样的感觉吗?| https://avatars.githubusercontent.com/u/9300978 +胡编技术站 | https://blog.kaifa.dev | 西子湖畔,擅长胡编 | https://avatars.githubusercontent.com/u/20921136 +杂货间 | https://blog.dazhidayong.cn | 强迫症 有态度 无情绪 程序员 | https://avatars.githubusercontent.com/u/20350703 +笑话人生 | https://www.cylong.com | 见兔而顾犬,未为晚也;
亡羊而补牢,未为迟也。 | https://www.cylong.com/images/avatar.jpg +{% endlinkgrid %} + +## 网站 + +{% linkgrid %} +TwoDragonLake | https://twodragonlake.com | 二龙湖 | https://avatars.githubusercontent.com/u/19360910 +{% endlinkgrid %} diff --git a/source/tags/index.md b/source/tags/index.md new file mode 100644 index 000000000..286c05569 --- /dev/null +++ b/source/tags/index.md @@ -0,0 +1,6 @@ +--- +title: 标签 +type: tags +date: 2018-03-24 22:36:28 +comments: false +--- diff --git a/tags/Alibaba/index.html b/tags/Alibaba/index.html deleted file mode 100644 index 0ab100513..000000000 --- a/tags/Alibaba/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Alibaba | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Alibaba - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Android-Studio/index.html b/tags/Android-Studio/index.html deleted file mode 100644 index 5128b8171..000000000 --- a/tags/Android-Studio/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Android Studio | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Android Studio - 标签 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Build/index.html b/tags/Build/index.html deleted file mode 100644 index 73e24aa19..000000000 --- a/tags/Build/index.html +++ /dev/null @@ -1,443 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Build | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Build - 标签 -

-
- - -
- 2020 -
- - -
- 2018 -
- - - - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Charles/index.html b/tags/Charles/index.html deleted file mode 100644 index f0d875c96..000000000 --- a/tags/Charles/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Charles | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Charles - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Deploy/index.html b/tags/Deploy/index.html deleted file mode 100644 index d6f6548c5..000000000 --- a/tags/Deploy/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Deploy | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Deploy - 标签 -

-
- - -
- 2019 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/DevTool/index.html b/tags/DevTool/index.html deleted file mode 100644 index 2c57550c8..000000000 --- a/tags/DevTool/index.html +++ /dev/null @@ -1,386 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: DevTool | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

DevTool - 标签 -

-
- - -
- 2021 -
- - -
- 2019 -
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Download/index.html b/tags/Download/index.html deleted file mode 100644 index 49dd4dea0..000000000 --- a/tags/Download/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Download | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Download - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Exp/index.html b/tags/Exp/index.html deleted file mode 100644 index 5ee0c664b..000000000 --- a/tags/Exp/index.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Exp | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Exp - 标签 -

-
- - -
- 2020 -
- - - - - - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Fiddler/index.html b/tags/Fiddler/index.html deleted file mode 100644 index d97349eac..000000000 --- a/tags/Fiddler/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Fiddler | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Fiddler - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/FileUpdate/index.html b/tags/FileUpdate/index.html deleted file mode 100644 index d6ebaced8..000000000 --- a/tags/FileUpdate/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: FileUpdate | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

FileUpdate - 标签 -

-
- - -
- 2021 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Flowable/index.html b/tags/Flowable/index.html deleted file mode 100644 index 2c970ebfa..000000000 --- a/tags/Flowable/index.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Flowable | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Flowable - 标签 -

-
- - -
- 2020 -
- - - - -
- 2019 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/GDD/index.html b/tags/GDD/index.html deleted file mode 100644 index 8d43b7e33..000000000 --- a/tags/GDD/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: GDD | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

GDD - 标签 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Gitlab/index.html b/tags/Gitlab/index.html deleted file mode 100644 index 6e5d01f55..000000000 --- a/tags/Gitlab/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Gitlab | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Gitlab - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Gradle/index.html b/tags/Gradle/index.html deleted file mode 100644 index 663fb86b5..000000000 --- a/tags/Gradle/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Gradle | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Gradle - 标签 -

-
- - -
- 2020 -
- - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Http/index.html b/tags/Http/index.html deleted file mode 100644 index d2ca2fabb..000000000 --- a/tags/Http/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Http | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Http - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Https/index.html b/tags/Https/index.html deleted file mode 100644 index fc18e39d7..000000000 --- a/tags/Https/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Https | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Https - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Init/index.html b/tags/Init/index.html deleted file mode 100644 index 3f72dd2bc..000000000 --- a/tags/Init/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Init | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Init - 标签 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Interview/index.html b/tags/Interview/index.html deleted file mode 100644 index 36994f3a7..000000000 --- a/tags/Interview/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Interview | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Interview - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/JDK8/index.html b/tags/JDK8/index.html deleted file mode 100644 index b5700e631..000000000 --- a/tags/JDK8/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: JDK8 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

JDK8 - 标签 -

-
- - -
- 2020 -
- - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/JetBrains/index.html b/tags/JetBrains/index.html deleted file mode 100644 index d510d9c9a..000000000 --- a/tags/JetBrains/index.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: JetBrains | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

JetBrains - 标签 -

-
- - -
- 2020 -
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/License/index.html b/tags/License/index.html deleted file mode 100644 index 6b2c6c4d6..000000000 --- a/tags/License/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: License | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

License - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Location/index.html b/tags/Location/index.html deleted file mode 100644 index 31460d6a9..000000000 --- a/tags/Location/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Location | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Location - 标签 -

-
- - -
- 2019 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Microservices/index.html b/tags/Microservices/index.html deleted file mode 100644 index e2510dfdb..000000000 --- a/tags/Microservices/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Microservices | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Microservices - 标签 -

-
- - -
- 2020 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/MySQL/index.html b/tags/MySQL/index.html deleted file mode 100644 index 622510bb0..000000000 --- a/tags/MySQL/index.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: MySQL | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

MySQL - 标签 -

-
- - -
- 2019 -
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Mybatis/index.html b/tags/Mybatis/index.html deleted file mode 100644 index abc56517d..000000000 --- a/tags/Mybatis/index.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Mybatis | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Mybatis - 标签 -

-
- - -
- 2020 -
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Netty/index.html b/tags/Netty/index.html deleted file mode 100644 index ed87bdec5..000000000 --- a/tags/Netty/index.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Netty | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Netty - 标签 -

-
- - -
- 2020 -
- - -
- 2019 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/OAuth2/index.html b/tags/OAuth2/index.html deleted file mode 100644 index 6029fe5d2..000000000 --- a/tags/OAuth2/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: OAuth2 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

OAuth2 - 标签 -

-
- - -
- 2020 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/OOAD/index.html b/tags/OOAD/index.html deleted file mode 100644 index db778d0b8..000000000 --- a/tags/OOAD/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: OOAD | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

OOAD - 标签 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/OSS/index.html b/tags/OSS/index.html deleted file mode 100644 index c88ca8ca2..000000000 --- a/tags/OSS/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: OSS | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

OSS - 标签 -

-
- - -
- 2021 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/OkHttp/index.html b/tags/OkHttp/index.html deleted file mode 100644 index 597884396..000000000 --- a/tags/OkHttp/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: OkHttp | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

OkHttp - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Protobuf/index.html b/tags/Protobuf/index.html deleted file mode 100644 index 0264eb2bd..000000000 --- a/tags/Protobuf/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Protobuf | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Protobuf - 标签 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/RAP/index.html b/tags/RAP/index.html deleted file mode 100644 index 9ed9b081f..000000000 --- a/tags/RAP/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: RAP | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

RAP - 标签 -

-
- - -
- 2018 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/RSA/index.html b/tags/RSA/index.html deleted file mode 100644 index 836165103..000000000 --- a/tags/RSA/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: RSA | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

RSA - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/RabbitMQ/index.html b/tags/RabbitMQ/index.html deleted file mode 100644 index 324332b55..000000000 --- a/tags/RabbitMQ/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: RabbitMQ | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

RabbitMQ - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Realm/index.html b/tags/Realm/index.html deleted file mode 100644 index 8779d22d1..000000000 --- a/tags/Realm/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Realm | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Realm - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/RocketMQ/index.html b/tags/RocketMQ/index.html deleted file mode 100644 index b6ec64368..000000000 --- a/tags/RocketMQ/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: RocketMQ | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

RocketMQ - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/RxJava/index.html b/tags/RxJava/index.html deleted file mode 100644 index ccd3e933c..000000000 --- a/tags/RxJava/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: RxJava | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

RxJava - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/SOA/index.html b/tags/SOA/index.html deleted file mode 100644 index 9036ce2f1..000000000 --- a/tags/SOA/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: SOA | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

SOA - 标签 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/SSD/index.html b/tags/SSD/index.html deleted file mode 100644 index b6ca029f6..000000000 --- a/tags/SSD/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: SSD | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

SSD - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Shell/index.html b/tags/Shell/index.html deleted file mode 100644 index 332570d88..000000000 --- a/tags/Shell/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Shell | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Shell - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Spring/index.html b/tags/Spring/index.html deleted file mode 100644 index f218db86a..000000000 --- a/tags/Spring/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Spring | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Spring - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/SpringBoot/index.html b/tags/SpringBoot/index.html deleted file mode 100644 index f3d32ffb8..000000000 --- a/tags/SpringBoot/index.html +++ /dev/null @@ -1,526 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: SpringBoot | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

SpringBoot - 标签 -

-
- - -
- 2020 -
- - - - - - - - - - - - - - -
- 2019 -
- - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/SpringBoot/page/2/index.html b/tags/SpringBoot/page/2/index.html deleted file mode 100644 index 8610727c9..000000000 --- a/tags/SpringBoot/page/2/index.html +++ /dev/null @@ -1,443 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: SpringBoot | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

SpringBoot - 标签 -

-
- - -
- 2019 -
- - - - - - - - - - - - - -
-
- - - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/SpringCloud/index.html b/tags/SpringCloud/index.html deleted file mode 100644 index 7c18a7b28..000000000 --- a/tags/SpringCloud/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: SpringCloud | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

SpringCloud - 标签 -

-
- - -
- 2020 -
- - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/SpringMVC/index.html b/tags/SpringMVC/index.html deleted file mode 100644 index ceadaeacf..000000000 --- a/tags/SpringMVC/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: SpringMVC | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

SpringMVC - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Summary/index.html b/tags/Summary/index.html deleted file mode 100644 index cdced580e..000000000 --- a/tags/Summary/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Summary | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Summary - 标签 -

-
- - -
- 2020 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Sync/index.html b/tags/Sync/index.html deleted file mode 100644 index def9026a8..000000000 --- a/tags/Sync/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Sync | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Sync - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Syncing/index.html b/tags/Syncing/index.html deleted file mode 100644 index 742ab9648..000000000 --- a/tags/Syncing/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Syncing | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Syncing - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Thrift/index.html b/tags/Thrift/index.html deleted file mode 100644 index 67af271bc..000000000 --- a/tags/Thrift/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Thrift | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Thrift - 标签 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Time/index.html b/tags/Time/index.html deleted file mode 100644 index af3ec2938..000000000 --- a/tags/Time/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Time | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Time - 标签 -

-
- - -
- 2020 -
- - - - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Top/index.html b/tags/Top/index.html deleted file mode 100644 index ea2f9a27d..000000000 --- a/tags/Top/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Top | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Top - 标签 -

-
- - -
- 2022 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/UML/index.html b/tags/UML/index.html deleted file mode 100644 index 5383d95b9..000000000 --- a/tags/UML/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: UML | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

UML - 标签 -

-
- - -
- 2019 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Util/index.html b/tags/Util/index.html deleted file mode 100644 index 8fe3a23b2..000000000 --- a/tags/Util/index.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Util | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Util - 标签 -

-
- - -
- 2019 -
- - - - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/VPN/index.html b/tags/VPN/index.html deleted file mode 100644 index e3b2e6d45..000000000 --- a/tags/VPN/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: VPN | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

VPN - 标签 -

-
- - -
- 2021 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Vant/index.html b/tags/Vant/index.html deleted file mode 100644 index 4f6c39aab..000000000 --- a/tags/Vant/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Vant | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Vant - 标签 -

-
- - -
- 2021 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Wechat/index.html b/tags/Wechat/index.html deleted file mode 100644 index c267b6c86..000000000 --- a/tags/Wechat/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Wechat | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Wechat - 标签 -

-
- - -
- 2021 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/Zxing/index.html b/tags/Zxing/index.html deleted file mode 100644 index 74dcb79bb..000000000 --- a/tags/Zxing/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: Zxing | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

Zxing - 标签 -

-
- - -
- 2019 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/flutter/index.html b/tags/flutter/index.html deleted file mode 100644 index 14ada8af4..000000000 --- a/tags/flutter/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: flutter | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

flutter - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/gRPC/index.html b/tags/gRPC/index.html deleted file mode 100644 index d4948aabc..000000000 --- a/tags/gRPC/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: gRPC | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

gRPC - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/git-account/index.html b/tags/git-account/index.html deleted file mode 100644 index d95a66e7b..000000000 --- a/tags/git-account/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: git account | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

git account - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/git-bash/index.html b/tags/git-bash/index.html deleted file mode 100644 index f9ccc68ab..000000000 --- a/tags/git-bash/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: git bash | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

git bash - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/git-signature/index.html b/tags/git-signature/index.html deleted file mode 100644 index 114d8e20a..000000000 --- a/tags/git-signature/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: git signature | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

git signature - 标签 -

-
- - -
- 2024 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/git-submodule/index.html b/tags/git-submodule/index.html deleted file mode 100644 index 42551dc45..000000000 --- a/tags/git-submodule/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: git submodule | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

git submodule - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/git-subtree/index.html b/tags/git-subtree/index.html deleted file mode 100644 index d7fa31484..000000000 --- a/tags/git-subtree/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: git subtree | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

git subtree - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/iTerm2/index.html b/tags/iTerm2/index.html deleted file mode 100644 index 3cc22ccea..000000000 --- a/tags/iTerm2/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: iTerm2 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

iTerm2 - 标签 -

-
- - -
- 2020 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/ignore/index.html b/tags/ignore/index.html deleted file mode 100644 index c1238cf70..000000000 --- a/tags/ignore/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: ignore | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

ignore - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/index.html b/tags/index.html deleted file mode 100644 index 433354942..000000000 --- a/tags/index.html +++ /dev/null @@ -1,336 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签 | 星海 - - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- - -
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/media/index.html b/tags/media/index.html deleted file mode 100644 index 9ce4edaed..000000000 --- a/tags/media/index.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: media | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

media - 标签 -

-
- - -
- 2018 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/note/index.html b/tags/note/index.html deleted file mode 100644 index d23110756..000000000 --- a/tags/note/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: note | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

note - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/vpn/index.html b/tags/vpn/index.html deleted file mode 100644 index 8b305ee18..000000000 --- a/tags/vpn/index.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: vpn | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

vpn - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/tags/\345\274\240\345\256\266\347\225\214/index.html" "b/tags/\345\274\240\345\256\266\347\225\214/index.html" deleted file mode 100644 index 0c75772d9..000000000 --- "a/tags/\345\274\240\345\256\266\347\225\214/index.html" +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: 张家界 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

张家界 - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/tags/\346\210\220\351\203\275/index.html" "b/tags/\346\210\220\351\203\275/index.html" deleted file mode 100644 index e08aedc6b..000000000 --- "a/tags/\346\210\220\351\203\275/index.html" +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: 成都 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

成都 - 标签 -

-
- - -
- 2020 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/tags/\346\213\211\350\220\250/index.html" "b/tags/\346\213\211\350\220\250/index.html" deleted file mode 100644 index de21101c2..000000000 --- "a/tags/\346\213\211\350\220\250/index.html" +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: 拉萨 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

拉萨 - 标签 -

-
- - -
- 2020 -
- - - - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/tags/\346\235\255\345\267\236/index.html" "b/tags/\346\235\255\345\267\236/index.html" deleted file mode 100644 index b9a0e3b90..000000000 --- "a/tags/\346\235\255\345\267\236/index.html" +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: 杭州 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

杭州 - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/tags/\346\277\200\346\210\230/index.html" "b/tags/\346\277\200\346\210\230/index.html" deleted file mode 100644 index 7eeac921e..000000000 --- "a/tags/\346\277\200\346\210\230/index.html" +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: 激战 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

激战 - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/tags/\350\277\267\345\256\253/index.html" "b/tags/\350\277\267\345\256\253/index.html" deleted file mode 100644 index 1b22148b1..000000000 --- "a/tags/\350\277\267\345\256\253/index.html" +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: 迷宫 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

迷宫 - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/tags/\351\225\277\346\262\231/index.html" "b/tags/\351\225\277\346\262\231/index.html" deleted file mode 100644 index 3677e0ddb..000000000 --- "a/tags/\351\225\277\346\262\231/index.html" +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: 长沙 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

长沙 - 标签 -

-
- - -
- 2020 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/tags/\351\273\204\345\261\261/index.html" "b/tags/\351\273\204\345\261\261/index.html" deleted file mode 100644 index 83275ec29..000000000 --- "a/tags/\351\273\204\345\261\261/index.html" +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -标签: 黄山 | 星海 - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

星海

- -
-

Life's A Struggle!

-
- - -
- - - - - - - -
- -
- -
- - - - - -
- -
- - - - - -
-
-
-

黄山 - 标签 -

-
- - -
- 2018 -
- - - -
-
- - - - -
-
- -
- -
- - - - -
- - 0% -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..c1ebcd4d4 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2113 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@adobe/css-tools@~4.3.1": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff" + integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ== + +"@babel/runtime@^7.10.2": + version "7.14.6" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" + integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== + dependencies: + regenerator-runtime "^0.13.4" + +"@leancloud/adapter-types@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@leancloud/adapter-types/-/adapter-types-3.0.0.tgz#71c5e8e37065bea4914650848b55a6262d658577" + integrity sha512-/1l2PWJ6pXizHphBorMN7B0d2YjmxZJf1s+bitvLALt7wBid5qbGpHqGGKE/yRdNlCKwl9FbXG1x5wUFZfQwHQ== + +"@leancloud/adapter-types@^5.0.0": + version "5.0.0" + resolved "https://registry.npmjs.org/@leancloud/adapter-types/-/adapter-types-5.0.0.tgz#38febab5232efee70744c09daa04ff053ebef70d" + integrity sha512-psnPaa4ONaA6X9y9xsjLmJXH+2spySH/YQUz59S0cZUTWVbZaUFwLQyHkv8OzZFixKqs+eV3xnWl7nUxCCCIeg== + +"@leancloud/adapter-utils@^1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@leancloud/adapter-utils/-/adapter-utils-1.2.2.tgz#e7e57b6a2d7ea949aa9889f33dec95ca94416a5d" + integrity sha512-B/bZM6WGN+sxMdZJeTWLAN/Gin00LX0E/M0MoygZhtrgCfCZSz47wgziOq5Fvl6yPifyvYBGaobydhyr7vxjxg== + +"@leancloud/adapters-superagent@^1.4.2": + version "1.4.2" + resolved "https://registry.npmjs.org/@leancloud/adapters-superagent/-/adapters-superagent-1.4.2.tgz#6d4b416a6e38be10509453b158bba744f03414f3" + integrity sha512-UpawevTXaIIFEpvtW6rm6PKH28i3OCIjgdJUmcFWbI8lezHz6bBznKB9g0aCRX8/c2h6SJThzjgqNlPny8I32g== + dependencies: + "@leancloud/adapter-types" "^5.0.0" + "@leancloud/adapter-utils" "^1.2.2" + "@types/superagent" "^4.1.7" + superagent "^5.2.2" + +"@leancloud/platform-adapters-browser@^1.1.0", "@leancloud/platform-adapters-browser@^1.5.2": + version "1.5.2" + resolved "https://registry.npmjs.org/@leancloud/platform-adapters-browser/-/platform-adapters-browser-1.5.2.tgz#5c5220479ea4801b3db0e0184599e4339bb1ee46" + integrity sha512-O7FRG4KvH20tiIP+dUbb7FE7kgkwEZifu7XmSRdPB5yO/+wR61BKykVQggQUdqJE6T9HZVSrqqbWWNqcMdJK6g== + dependencies: + "@leancloud/adapter-types" "^5.0.0" + "@leancloud/adapters-superagent" "^1.4.2" + +"@leancloud/platform-adapters-node@^1.1.0", "@leancloud/platform-adapters-node@^1.5.2": + version "1.5.2" + resolved "https://registry.npmjs.org/@leancloud/platform-adapters-node/-/platform-adapters-node-1.5.2.tgz#0b4f47bb73da0a063f24c970b6b6dda95d54edb0" + integrity sha512-FhvL6u0CXG33M5UCla2Rfja6Rrv8Sj1mrRWCZvI6m5sW4A7RyWFICW8akBL++uHUNP8WDc7szfrBz8TJHQWDcg== + dependencies: + "@leancloud/adapter-types" "^5.0.0" + "@leancloud/adapters-superagent" "^1.4.2" + "@types/ws" "^7.2.2" + localstorage-memory "^1.0.2" + ws "^5.2.2" + +"@leancloud/platform-adapters-weapp@^1.2.0", "@leancloud/platform-adapters-weapp@^1.6.1": + version "1.6.1" + resolved "https://registry.npmjs.org/@leancloud/platform-adapters-weapp/-/platform-adapters-weapp-1.6.1.tgz#20f8927769357491c19043396c0da14391c8260b" + integrity sha512-/Pxj0Zk9829OTQdt1KdfglqVBiOteber8gUzw5Kgjws1+hlPJ7x8x5VZcdPM3lnTh6UsP4f6CkOtS/QOeLu6JA== + dependencies: + "@leancloud/adapter-types" "^5.0.0" + "@leancloud/adapter-utils" "^1.2.2" + event-target-shim "^5.0.1" + miniprogram-api-typings "^2.10.2" + +"@types/cookiejar@*": + version "2.1.2" + resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" + integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== + +"@types/node@*": + version "15.12.2" + resolved "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz#1f2b42c4be7156ff4a6f914b2fb03d05fa84e38d" + integrity sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww== + +"@types/superagent@^4.1.7": + version "4.1.11" + resolved "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.11.tgz#4822bc64a82a0f579261a77097dbca276556c20e" + integrity sha512-cZkWBXZI+jESnUTp8RDGBmk1Zn2MkScP4V5bjD7DyqB7L0WNWpblh4KX5K/6aTqxFZMhfo1bhi2cwoAEDVBBJw== + dependencies: + "@types/cookiejar" "*" + "@types/node" "*" + +"@types/ws@^7.2.2": + version "7.4.4" + resolved "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz#93e1e00824c1de2608c30e6de4303ab3b4c0c9bc" + integrity sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ== + dependencies: + "@types/node" "*" + +a-sync-waterfall@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz#75b6b6aa72598b497a125e7a2770f14f4c8a1fa7" + integrity sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA== + +abbrev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" + integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== + +accepts@~1.3.5: + version "1.3.7" + resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +asap@^2.0.3: + version "2.0.6" + resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +ascli@~1: + version "1.0.1" + resolved "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz#bcfa5974a62f18e81cabaeb49732ab4a88f906bc" + integrity sha1-vPpZdKYvGOgcq660lzKrSoj5Brw= + dependencies: + colour "~0.7.1" + optjs "~3.2.2" + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +async@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-arraybuffer@^0.1.5: + version "0.1.5" + resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= + +basic-auth@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bluebird@^3.5.2, bluebird@^3.5.5, bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +bytebuffer@~5: + version "5.0.1" + resolved "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" + integrity sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0= + dependencies: + long "~3" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +camel-case@^4.0.0, camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase@^2.0.1: + version "2.1.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + +chalk@^4, chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +clipboard@^2.0.0: + version "2.0.8" + resolved "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz#ffc6c103dd2967a83005f3f61976aa4655a4cdba" + integrity sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + +cliui@^3.0.3: + version "3.2.0" + resolved "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colour@~0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" + integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g= + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-exists@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" + integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== + +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +component-emitter@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +connect@^3.7.0: + version "3.7.0" + resolved "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" + integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +cookiejar@^2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" + integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== + +core-js@^2.4.0: + version "2.6.12" + resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + +cross-spawn@^7.0.0, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + +cuid@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/cuid/-/cuid-2.1.8.tgz#cbb88f954171e0d5747606c0139fb65c5101eac0" + integrity sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.1: + version "4.3.1" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +debug@^4.3.2: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +decamelize@^1.1.1: + version "1.2.0" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +depd@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^3.0.0: + version "3.3.0" + resolved "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a" + integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA== + dependencies: + domelementtype "^2.0.1" + +domhandler@^4.0.0, domhandler@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059" + integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA== + dependencies: + domelementtype "^2.2.0" + +domhandler@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^2.0.0, domutils@^2.5.2: + version "2.7.0" + resolved "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz#8ebaf0c41ebafcf55b0b72ec31c56323712c5442" + integrity sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +domutils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +eastasianwidth@~0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +ejs@^3.1.6: + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== + dependencies: + jake "^10.8.5" + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^3.0.1, entities@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== + +entities@^4.2.0, entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +event-target-shim@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +eventemitter3@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" + integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= + +eventemitter3@^3.0.0: + version "3.1.2" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + +fast-equals@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-3.0.3.tgz#8e6cb4e51ca1018d87dd41982ef92758b3e4197f" + integrity sha512-NCe8qxnZFARSHGztGMZOO/PC1qa5MIFB5Hp66WdzbCRAz8U8US3bx1UTgLS49efBQPcUtO9gf5oVEY8o7y/7Kg== + +fast-safe-stringify@^2.0.7: + version "2.0.7" + resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" + integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + +filelist@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formidable@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" + integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.0.5, glob@^7.1.6: + version "7.1.7" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + +graceful-fs@^4.2.10: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hexo-blog-encrypt@^3.1.9: + version "3.1.9" + resolved "https://registry.yarnpkg.com/hexo-blog-encrypt/-/hexo-blog-encrypt-3.1.9.tgz#4579bbdb35aa040181942088e539a44b17be6e59" + integrity sha512-dzuZiW5mD8t1FOhJXqc+IgV1Tc45YtatJb/w8k4PyT9McQxsROQXsBw+zbDembzuqMnbnkCeT2SapZ/r9AgAcw== + +hexo-cli@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/hexo-cli/-/hexo-cli-4.3.2.tgz#7e7d02da137d9fa461a4356964793c56e92b29e6" + integrity sha512-druJeBgLpG9ncDS5AhBHdAXk0G4CFj8Qes09pApyZ6bR+nJW1JYiDMuilhudaKDdq+1l49jWXVTidkcb7p0Jbw== + dependencies: + abbrev "^2.0.0" + bluebird "^3.7.2" + command-exists "^1.2.9" + hexo-fs "^4.1.1" + hexo-log "^4.0.1" + hexo-util "^3.3.0" + minimist "^1.2.5" + picocolors "^1.0.0" + resolve "^1.20.0" + tildify "^2.0.0" + +hexo-deployer-git@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hexo-deployer-git/-/hexo-deployer-git-4.0.0.tgz#d507ecd5ba7d07f60b0c1ab50ced3e47535f6017" + integrity sha512-28t1Q+4taB/UaBAP52W3mD/wcCwa2y2zBieUfBJFBZudbmVgiKJB5YedYILeyI5QByaUKAOwoupmdTbocdQ+CQ== + dependencies: + bluebird "^3.7.2" + hexo-fs "^4.0.0" + hexo-util "^2.7.0" + luxon "^3.0.4" + nunjucks "^3.2.3" + picocolors "^1.0.0" + +hexo-front-matter@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/hexo-front-matter/-/hexo-front-matter-4.2.1.tgz#c28ee74f66ab76c98fa73877b5ff7a9cc14161cc" + integrity sha512-sJJI0GNmejYiwBvgnGRKn5V3sbODB4dNPr8jyw2Qp0PRHr4Uuyv8iyxw6WfK3+T7yvzYvJOh+tZ7jnwr2BYARA== + dependencies: + js-yaml "^4.1.0" + +hexo-fs@^4.0.0, hexo-fs@^4.1.1, hexo-fs@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/hexo-fs/-/hexo-fs-4.1.3.tgz#c64c66e230dad4ec08ca21244327aa0aba69218a" + integrity sha512-Q92zQ5PlVDouvSWFLXQoFSTLIUIODikUJs2BfAXQglyOEjN1dOQn1Z5Nimk/7GHof17R5h/uObCQLnZAjzI2tg== + dependencies: + bluebird "^3.7.2" + chokidar "^3.5.3" + graceful-fs "^4.2.10" + hexo-util "^3.0.1" + +hexo-generator-archive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hexo-generator-archive/-/hexo-generator-archive-2.0.0.tgz#bd93f17848843bb5bface81103d81f5be1a8b2c9" + integrity sha512-KikJk7dGFbtNHOgqtLFGf5T/S8n1paGp+Gy0KfVDz+HKYhGbXOouyiZkmc3O9KrYt6ja14rmkMhq7KKGtvfehw== + dependencies: + hexo-pagination "3.0.0" + +hexo-generator-category@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hexo-generator-category/-/hexo-generator-category-2.0.0.tgz#55473d5fafaec1cab7a5d9c0da7b159c0133d65c" + integrity sha512-9OduRBf3WeRDa4BR0kAfRjOVHur7v3fm0NKAwbjUiqULigAdNZVZPO3cHKW2MlBbl/lI5PuWdhQ9zZ99CCCAgQ== + dependencies: + hexo-pagination "3.0.0" + +hexo-generator-feed@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/hexo-generator-feed/-/hexo-generator-feed-3.0.0.tgz#4126ef5e308264c42599fb0efdaf88ed11fa599e" + integrity sha512-Jo35VSRSNeMitS2JmjCq3OHAXXYU4+JIODujHtubdG/NRj2++b3Tgyz9pwTmROx6Yxr2php/hC8og5AGZHh8UQ== + dependencies: + hexo-util "^2.1.0" + nunjucks "^3.0.0" + +hexo-generator-index@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hexo-generator-index/-/hexo-generator-index-3.0.0.tgz#4c9233731e027a6af6491886a4aafe08ffdaffe0" + integrity sha512-83AuNN4cWdLVi//3ugR8E3kR6rrOwhXZt+hOCm1IjtIGj353/GlrtpMHpqZHU5kqipzj4miy9dweVdukXglVWw== + dependencies: + hexo-pagination "3.0.0" + +hexo-generator-searchdb@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/hexo-generator-searchdb/-/hexo-generator-searchdb-1.4.1.tgz#e33c9bd22130a6705673e889b6497fcc3fe80fa5" + integrity sha512-7m8IBpZbI6iKb2jRYxs4pghD6Ln8ylQSRGl6MIC4G9wws21vYSXSD8rvC3MoCO+pWBHs6E/mTA/rjG+p2AZfVg== + dependencies: + nunjucks "^3.2.2" + +hexo-generator-tag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hexo-generator-tag/-/hexo-generator-tag-2.0.0.tgz#f5a18c92b2f28063a76008b4090a7cea81fbbcc0" + integrity sha512-1px/hF3veEohWDN8jjzchQhaiz+uOStUvvMaBJC9vWOlALh30UFcapL8IrvAwwJZjFRVA+WqGgDRqoQ8+yaaFw== + dependencies: + hexo-pagination "3.0.0" + +hexo-hide-posts@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/hexo-hide-posts/-/hexo-hide-posts-0.4.3.tgz#5b8147f88b5225c06fcc77d7bd861f37ed55e999" + integrity sha512-5VLzaX6ntJ0uJy56X5tgbII6oucBoUS5gCzm37ozXibMT8lBmRgbuzzayKNuBL+oQpyAa+vEsLyxlSunyaRs7g== + dependencies: + chalk "^4" + +hexo-i18n@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hexo-i18n/-/hexo-i18n-2.0.0.tgz#ec7abc160ffef202524f3ff8076b85d5701c447b" + integrity sha512-dkUXecEtChaQMdTHN4WR13c8GwKqjbSOZPJS9qDqV6Ebnb77Wa/nQzWFckhP0dCps3a9lUQBd8hYGOMbOosiQQ== + dependencies: + sprintf-js "^1.1.2" + +hexo-leancloud-counter-security@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/hexo-leancloud-counter-security/-/hexo-leancloud-counter-security-1.5.0.tgz#e86c7175d816c6681d6270e781702814e40b53b2" + integrity sha512-oPQh2XK/cHG7izeq4rUhsrKKExtgpl2+BIqJXLY6E84nveMHmjvXwrX8/lYY4EUeFUOaXvkXR6i7InD2rLyZrA== + dependencies: + leancloud-storage "^4.3.0" + readline-sync "^1.4.10" + +hexo-log@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hexo-log/-/hexo-log-4.1.0.tgz#54b42c250335067b5c60b4137f501607454efda0" + integrity sha512-i2Sgxk8Cgx5viSjq5qW5N/rBFfwoCKQcH8qnnW1fawCapcdEAhIsq+Y3vbrs9bssyDlyU6Vqm4oQmosREaNI7Q== + dependencies: + picocolors "^1.0.0" + +hexo-pagination@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hexo-pagination/-/hexo-pagination-3.0.0.tgz#b98b050bbddff25ae646e3d00ad607ac6ae77ea7" + integrity sha512-8oo1iozloZo7TojPVYg4IxL3SJKCBdSJ908fTlIxIK7TWJIKdYnQlW31+12DBJ0NhVZA/lZisPObGF08wT8fKw== + +hexo-renderer-ejs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hexo-renderer-ejs/-/hexo-renderer-ejs-2.0.0.tgz#56e0c3de5f6b0e1e68b923c65a7c8bfe9ff715eb" + integrity sha512-qCjE1IdwgDgv65qyb0KMVCwCdSVAkH0vwAe9XihjvaKWkmb9dtt8DgErOdqCXn0HReSyWiEVP2BrLRj3gyHwOQ== + dependencies: + ejs "^3.1.6" + +hexo-renderer-markdown-it@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/hexo-renderer-markdown-it/-/hexo-renderer-markdown-it-7.1.1.tgz#30997efd6e5972056e4c37a986ab68cbe891fed9" + integrity sha512-BxI2j2f/l7lOgb7DiT1M4GcP/QhR8/rjMlYx4MEPog/9NTpYhbaspiVsw3tGXOsZVmu+cVgBYoeyIQsFYvv3rw== + dependencies: + hexo-util "^3.0.1" + markdown-it "^13.0.1" + markdown-it-abbr "^1.0.4" + markdown-it-attrs "^4.1.3" + markdown-it-cjk-breaks "^1.1.2" + markdown-it-container "^3.0.0" + markdown-it-deflist "^2.0.3" + markdown-it-emoji "^2.0.0" + markdown-it-footnote "^3.0.1" + markdown-it-ins "^3.0.0" + markdown-it-mark "^3.0.0" + markdown-it-sub "^1.0.0" + markdown-it-sup "^1.0.0" + +hexo-renderer-stylus@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/hexo-renderer-stylus/-/hexo-renderer-stylus-3.0.1.tgz#7ff6f3101fa86ba25de415257be7fa7032e910bc" + integrity sha512-cFm8ZwShBBeFcQwOXc8EK7lIZnSYVD6OJykdL4GBw99hxc4eD5Hlsi32nRzE8sgKv00jhX1s9Da3GVVFMPAVQg== + dependencies: + nib "^1.2.0" + stylus "^0.62.0" + +hexo-server@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hexo-server/-/hexo-server-3.0.0.tgz#fcc597b29b72ee1f035824c5ebd3d92f7e1adb1c" + integrity sha512-u4s0ty9Aew6jV+a9oMrXBwhrRpUQ0U8PWM/88a5aHgDru58VY81mVrxOFxs788NAsWQ8OvsJtF5m7mnXoRnSIA== + dependencies: + bluebird "^3.5.5" + compression "^1.7.4" + connect "^3.7.0" + mime "^3.0.0" + morgan "^1.9.1" + open "^8.0.9" + picocolors "^1.0.0" + serve-static "^1.14.1" + +hexo-symbols-count-time@^0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/hexo-symbols-count-time/-/hexo-symbols-count-time-0.7.1.tgz#7d41c549827071756bd2730047eea027f6dfd631" + integrity sha512-Zw19uxRIT/3VCvMQytG6HfBJc/IvfFJYui8cg6vyFAtDxkPOoZ1UlsPjrBRXYAqO6VjLc/vRi+Jthj38pKNPlw== + dependencies: + hexo-util "1.9.0" + +hexo-theme-next@^8.20.0: + version "8.20.0" + resolved "https://registry.yarnpkg.com/hexo-theme-next/-/hexo-theme-next-8.20.0.tgz#8130f1b32d353f4bde7e99aa7d9f8f1e682d37b4" + integrity sha512-cLKE32mP6B2E84+4XUXOgZhaI+srndHAviVpDaz8S7fj4OTnRchWg1anbhpZTFAgMO0NSRs4A0kT61eXY3l/Gg== + +hexo-util@1.9.0: + version "1.9.0" + resolved "https://registry.npmjs.org/hexo-util/-/hexo-util-1.9.0.tgz#0018429319720599253b2a3ddde0c5e4e4cfc652" + integrity sha512-WXv8IYd9HFtP6u/y7uoI//Fmg88uhKKDto9KeNNRdWf4HG/bRh/1NcSQZWu81DOZNshWD1rvFU8OKb7bUnX1WA== + dependencies: + bluebird "^3.5.2" + camel-case "^4.0.0" + cross-spawn "^7.0.0" + deepmerge "^4.2.2" + highlight.js "^9.13.1" + htmlparser2 "^4.0.0" + prismjs "^1.17.1" + punycode.js "^2.1.0" + strip-indent "^3.0.0" + striptags "^3.1.1" + +hexo-util@^2.1.0: + version "2.5.0" + resolved "https://registry.npmjs.org/hexo-util/-/hexo-util-2.5.0.tgz#de5635341116ba2563a789a0ebceb55a875221ad" + integrity sha512-l0zkqcg2524KPO84HQe0JROpPlCM/dEnCJaJrZ1qsq+3+/YxhDa0zxiGtUVY1dtrWzOK/V11Zj+UEklhGP8Jeg== + dependencies: + bluebird "^3.5.2" + camel-case "^4.0.0" + cross-spawn "^7.0.0" + deepmerge "^4.2.2" + highlight.js "^10.7.1" + htmlparser2 "^6.0.0" + prismjs "^1.17.1" + strip-indent "^3.0.0" + +hexo-util@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/hexo-util/-/hexo-util-2.7.0.tgz#13d09292e79d260db35399710b3e19f3995443a3" + integrity sha512-hQM3h34nhDg0bSe/Tg1lnpODvNkz7h2u0+lZGzlKL0Oufp+5KCAEUX9wal7/xC7ax3/cwEn8IuoU75kNpZLpJQ== + dependencies: + bluebird "^3.5.2" + camel-case "^4.0.0" + cross-spawn "^7.0.0" + deepmerge "^4.2.2" + highlight.js "^11.0.1" + htmlparser2 "^7.0.0" + prismjs "^1.17.1" + strip-indent "^3.0.0" + +hexo-util@^3.0.1, hexo-util@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/hexo-util/-/hexo-util-3.3.0.tgz#448927fb22e167f2159306666cc2c7e82c777513" + integrity sha512-YvGngXijE2muEh5L/VI4Fmjqb+/yAkmY+VuyhWVoRwQu1X7bmWodsfYRXX7CUYhi5LqsvH8FAe/yBW1+f6ZX4Q== + dependencies: + camel-case "^4.1.2" + cross-spawn "^7.0.3" + deepmerge "^4.2.2" + highlight.js "^11.6.0" + htmlparser2 "^9.0.0" + prismjs "^1.29.0" + strip-indent "^3.0.0" + +hexo@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/hexo/-/hexo-7.2.0.tgz#0c2b0eeb3f16c557638b6dbd5956a425460d7d0f" + integrity sha512-RYIzl7jfG0i2jH/k5IZg4C1anyHfmKHNUsBKIn9LU0V3iQ0WQrwuOLFDJwaZDenqmzHYJhAVCGAkrBDfF/IlVg== + dependencies: + abbrev "^2.0.0" + archy "^1.0.0" + bluebird "^3.7.2" + hexo-cli "^4.3.2" + hexo-front-matter "^4.2.1" + hexo-fs "^4.1.3" + hexo-i18n "^2.0.0" + hexo-log "^4.0.1" + hexo-util "^3.3.0" + js-yaml "^4.1.0" + js-yaml-js-types "^1.0.0" + micromatch "^4.0.4" + moize "^6.1.6" + moment "^2.29.1" + moment-timezone "^0.5.34" + nunjucks "^3.2.3" + picocolors "^1.0.0" + pretty-hrtime "^1.0.3" + resolve "^1.22.0" + strip-ansi "^6.0.0" + text-table "^0.2.0" + tildify "^2.0.0" + titlecase "^1.1.3" + warehouse "^5.0.1" + +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + +highlight.js@^11.0.1: + version "11.5.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.5.1.tgz#027c24e4509e2f4dcd00b4a6dda542ce0a1f7aea" + integrity sha512-LKzHqnxr4CrD2YsNoIf/o5nJ09j4yi/GcH5BnYz9UnVpZdS4ucMgvP61TDty5xJcFGRjnH4DpujkS9bHT3hq0Q== + +highlight.js@^11.6.0: + version "11.9.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" + integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== + +highlight.js@^9.13.1: + version "9.18.5" + resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" + integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== + +htmlparser2@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" + integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q== + dependencies: + domelementtype "^2.0.1" + domhandler "^3.0.0" + domutils "^2.0.0" + entities "^2.0.0" + +htmlparser2@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +htmlparser2@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-7.2.0.tgz#8817cdea38bbc324392a90b1990908e81a65f5a5" + integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.2" + domutils "^2.8.0" + entities "^3.0.1" + +htmlparser2@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" + integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.1.0" + entities "^4.5.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@~1.1.6: + version "1.1.6" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-core-module@^2.13.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" + integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== + dependencies: + hasown "^2.0.2" + +is-core-module@^2.8.1: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + dependencies: + has "^1.0.3" + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.1" + minimatch "^3.0.4" + +javascript-state-machine@^2.3.5: + version "2.4.0" + resolved "https://registry.npmjs.org/javascript-state-machine/-/javascript-state-machine-2.4.0.tgz#d8be31ec38f24ac1a1832f0b672fc3cd5f79c96e" + integrity sha1-2L4x7DjySsGhgy8LZy/DzV95yW4= + +js-yaml-js-types@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/js-yaml-js-types/-/js-yaml-js-types-1.0.0.tgz#bf17cb75c7587c698294d15bbfa5f9b8b10b874b" + integrity sha512-UNjPwuoaj4mcHkJCJSF6l4MgkzoFjG+JJkBXMYNvjgO3yE9gTeRt+E6PN022vduz/daZZ7HmlEiSEE36NrGE4w== + dependencies: + esprima "^4.0.1" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsonparse@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + dependencies: + invert-kv "^1.0.0" + +leancloud-realtime-plugin-live-query@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/leancloud-realtime-plugin-live-query/-/leancloud-realtime-plugin-live-query-1.2.0.tgz#1e07da1b5b99e44cddbc3b052234422f278c4ab9" + integrity sha512-eJooIH8/FyUoozr3Eeby2DpDnmX39m1bfxfxlYPuojkio+i/DLwPD+aTHnRDH6QXJcT6tNTt85RcxVR/Txg98Q== + +leancloud-realtime@^5.0.0-rc.4: + version "5.0.0-rc.6" + resolved "https://registry.npmjs.org/leancloud-realtime/-/leancloud-realtime-5.0.0-rc.6.tgz#63bdff9e710185fa37a321c52c14ef5ee76acb00" + integrity sha512-WJExh9oY+KoGgUyBZ348AuPG34pvxnNgJD3W054ERJos0DjJsxwpeG+flcBMXuSYGxt5huQ+Pxko3zefJFiLQQ== + dependencies: + "@babel/runtime" "^7.10.2" + "@leancloud/adapter-types" "^3.0.0" + "@leancloud/platform-adapters-browser" "^1.1.0" + "@leancloud/platform-adapters-node" "^1.1.0" + "@leancloud/platform-adapters-weapp" "^1.2.0" + base64-arraybuffer "^0.1.5" + debug "^3.1.0" + eventemitter3 "^3.0.0" + javascript-state-machine "^2.3.5" + lodash "^4.17.10" + promise-timeout "^1.3.0" + protobufjs "^5.0.1" + uuid "^3.0.0" + +leancloud-storage@^4.3.0: + version "4.11.1" + resolved "https://registry.npmjs.org/leancloud-storage/-/leancloud-storage-4.11.1.tgz#9ba7ecf2b083aeabafc92acd6bbafc31426aec74" + integrity sha512-cNlgCjyZtdGWWUFnnDvvcoiBKjFY13iQT8PfrX0uzRqvm3XmKwkMOJnTE4AuL7fbjL1HsN4oEO2hlV9QOnAmxw== + dependencies: + "@leancloud/adapter-types" "^5.0.0" + "@leancloud/platform-adapters-browser" "^1.5.2" + "@leancloud/platform-adapters-node" "^1.5.2" + "@leancloud/platform-adapters-weapp" "^1.6.1" + babel-runtime "^6.26.0" + debug "^3.1.0" + eventemitter3 "^2.0.3" + leancloud-realtime "^5.0.0-rc.4" + leancloud-realtime-plugin-live-query "^1.2.0" + md5 "^2.0.0" + promise-timeout "^1.3.0" + underscore "^1.8.3" + uuid "^3.3.2" + +linkify-it@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec" + integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw== + dependencies: + uc.micro "^1.0.1" + +localstorage-memory@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/localstorage-memory/-/localstorage-memory-1.0.3.tgz#566b37968fe0c4d76ba36a6da564fa613945ca72" + integrity sha512-t9P8WB6DcVttbw/W4PIE8HOqum8Qlvx5SjR6oInwR9Uia0EEmyUeBh7S+weKByW+l/f45Bj4L/dgZikGFDM6ng== + +lodash@^4.17.10: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +long@~3: + version "3.2.0" + resolved "https://registry.npmjs.org/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" + integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +luxon@^3.0.4: + version "3.4.4" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" + integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== + +markdown-it-abbr@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/markdown-it-abbr/-/markdown-it-abbr-1.0.4.tgz#d66b5364521cbb3dd8aa59dadfba2fb6865c8fd8" + integrity sha1-1mtTZFIcuz3Yqlna37ovtoZcj9g= + +markdown-it-attrs@^4.1.3: + version "4.1.6" + resolved "https://registry.yarnpkg.com/markdown-it-attrs/-/markdown-it-attrs-4.1.6.tgz#2bc331c7649d8c6396a0613c2bba1093f3e64da9" + integrity sha512-O7PDKZlN8RFMyDX13JnctQompwrrILuz2y43pW2GagcwpIIElkAdfeek+erHfxUOlXWPsjFeWmZ8ch1xtRLWpA== + +markdown-it-cjk-breaks@^1.1.2: + version "1.1.3" + resolved "https://registry.npmjs.org/markdown-it-cjk-breaks/-/markdown-it-cjk-breaks-1.1.3.tgz#11cf76ab271f68b5ae0c6747747f404631b73545" + integrity sha512-/gX3LueMp+5FdUkqcFPK5nHI6t85uq1rMv8yhrmCOZhU90XqybQj8OT1hVrxrdseajaHLPBK43xLzEKPosgTDA== + dependencies: + eastasianwidth "~0.2.0" + +markdown-it-container@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-3.0.0.tgz#1d19b06040a020f9a827577bb7dbf67aa5de9a5b" + integrity sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw== + +markdown-it-deflist@^2.0.3: + version "2.1.0" + resolved "https://registry.npmjs.org/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz#50d7a56b9544cd81252f7623bd785e28a8dcef5c" + integrity sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg== + +markdown-it-emoji@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz#cd42421c2fda1537d9cc12b9923f5c8aeb9029c8" + integrity sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ== + +markdown-it-footnote@^3.0.1: + version "3.0.3" + resolved "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-3.0.3.tgz#e0e4c0d67390a4c5f0c75f73be605c7c190ca4d8" + integrity sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w== + +markdown-it-ins@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-3.0.1.tgz#c09356b917cf1dbf73add0b275d67ab8c73d4b4d" + integrity sha512-32SSfZqSzqyAmmQ4SHvhxbFqSzPDqsZgMHDwxqPzp+v+t8RsmqsBZRG+RfRQskJko9PfKC2/oxyOs4Yg/CfiRw== + +markdown-it-mark@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz#51257db58787d78aaf46dc13418d99a9f3f0ebd3" + integrity sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A== + +markdown-it-sub@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz#375fd6026eae7ddcb012497f6411195ea1e3afe8" + integrity sha1-N1/WAm6ufdywEkl/ZBEZXqHjr+g= + +markdown-it-sup@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz#cb9c9ff91a5255ac08f3fd3d63286e15df0a1fc3" + integrity sha1-y5yf+RpSVawI8/09YyhuFd8KH8M= + +markdown-it@^13.0.1: + version "13.0.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.2.tgz#1bc22e23379a6952e5d56217fbed881e0c94d536" + integrity sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w== + dependencies: + argparse "^2.0.1" + entities "~3.0.1" + linkify-it "^4.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + +md5@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + +methods@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +micro-memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/micro-memoize/-/micro-memoize-4.1.2.tgz#ce719c1ba1e41592f1cd91c64c5f41dcbf135f36" + integrity sha512-+HzcV2H+rbSJzApgkj0NdTakkC+bnyeiUxgT6/m7mjcz1CmM22KYFKp+EVj1sWe4UYcnriJr5uqHQD/gMHLD+g== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.48.0, "mime-db@>= 1.43.0 < 2": + version "1.48.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" + integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== + +mime-types@^2.1.12, mime-types@~2.1.24: + version "2.1.31" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" + integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== + dependencies: + mime-db "1.48.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.4.6: + version "2.5.2" + resolved "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +miniprogram-api-typings@^2.10.2: + version "2.12.0" + resolved "https://registry.npmjs.org/miniprogram-api-typings/-/miniprogram-api-typings-2.12.0.tgz#7a29c90f3e5efa36588422d1f01e22d3394aaaa1" + integrity sha512-ibvbqeslVFur0IAvTxLMvsbtvVcMo6gwvOnj0YZHV7aeDLu091VQRrETT2QuiG9P6aZWRcxeNGJChRKVPCp9VQ== + +moize@^6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/moize/-/moize-6.1.6.tgz#ac2e723e74b951875fe2c0c3433405c2b098c3e6" + integrity sha512-vSKdIUO61iCmTqhdoIDrqyrtp87nWZUmBPniNjO0fX49wEYmyDO4lvlnFXiGcaH1JLE/s/9HbiK4LSHsbiUY6Q== + dependencies: + fast-equals "^3.0.1" + micro-memoize "^4.1.2" + +moment-timezone@^0.5.34: + version "0.5.34" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c" + integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0": + version "2.29.1" + resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + +moment@^2.29.1: + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== + +morgan@^1.9.1: + version "1.10.0" + resolved "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" + integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ== + dependencies: + basic-auth "~2.0.1" + debug "2.6.9" + depd "~2.0.0" + on-finished "~2.3.0" + on-headers "~1.0.2" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +nib@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/nib/-/nib-1.2.0.tgz#cf650a975307edaa8683470430f82ba132bf9f7b" + integrity sha512-7HgrnMl/3yOmWykueO8/D0q+0iWwe7Z+CK2Eaq/xQV8w1hK80WN1oReRQkfkrztbAAnp/nTHkUSl5EcVkor6JQ== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +nunjucks@^3.0.0, nunjucks@^3.2.2, nunjucks@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz#1b33615247290e94e28263b5d855ece765648a31" + integrity sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ== + dependencies: + a-sync-waterfall "^1.0.0" + asap "^2.0.3" + commander "^5.1.0" + +object-inspect@^1.9.0: + version "1.10.3" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" + integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +open@^8.0.9: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +optjs@~3.2.2: + version "3.2.2" + resolved "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee" + integrity sha1-aabOicRCpEQDFBrS+bNwvVu29O4= + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.0" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pretty-hrtime@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + +prismjs@^1.17.1: + version "1.23.0" + resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz#d3b3967f7d72440690497652a9d40ff046067f33" + integrity sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA== + optionalDependencies: + clipboard "^2.0.0" + +prismjs@^1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + +promise-timeout@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/promise-timeout/-/promise-timeout-1.3.0.tgz#d1c78dd50a607d5f0a5207410252a3a0914e1014" + integrity sha512-5yANTE0tmi5++POym6OgtFmwfDvOXABD9oj/jLQr5GPEyuNEb7jH4wbbANJceJid49jwhi1RddxnhnEAb/doqg== + +protobufjs@^5.0.1: + version "5.0.3" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz#e4dfe9fb67c90b2630d15868249bcc4961467a17" + integrity sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA== + dependencies: + ascli "~1" + bytebuffer "~5" + glob "^7.0.5" + yargs "^3.10.0" + +punycode.js@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/punycode.js/-/punycode.js-2.1.0.tgz#f3937f7a914152c2dc17e9c280a2cf86a26b7cda" + integrity sha512-LvGUJ9QHiESLM4yn8JuJWicstRcJKRmP46psQw1HvCZ9puLFwYMKJWvkAkP3OHBVzNzZGx/D53EYJrIaKd9gZQ== + +qs@^6.9.4: + version "6.10.1" + resolved "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" + integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== + dependencies: + side-channel "^1.0.4" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +readable-stream@3: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +readline-sync@^1.4.10: + version "1.4.10" + resolved "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" + integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + +resolve@^1.20.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rfdc@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +sax@~1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== + +select@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + +semver@^7.3.2: + version "7.3.5" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +send@0.17.1: + version "0.17.1" + resolved "https://registry.npmjs.org/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@^1.14.1: + version "1.14.1" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +sprintf-js@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +striptags@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz#c8c3e7fdd6fb4bb3a32a3b752e5b5e3e38093ebd" + integrity sha1-yMPn/db7S7OjKjt1LltePjgJPr0= + +stylus@^0.62.0: + version "0.62.0" + resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.62.0.tgz#648a020e2bf90ed87587ab9c2f012757e977bb5d" + integrity sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg== + dependencies: + "@adobe/css-tools" "~4.3.1" + debug "^4.3.2" + glob "^7.1.6" + sax "~1.3.0" + source-map "^0.7.3" + +superagent@^5.2.2: + version "5.3.1" + resolved "https://registry.npmjs.org/superagent/-/superagent-5.3.1.tgz#d62f3234d76b8138c1320e90fa83dc1850ccabf1" + integrity sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.2" + debug "^4.1.1" + fast-safe-stringify "^2.0.7" + form-data "^3.0.0" + formidable "^1.2.2" + methods "^1.1.2" + mime "^2.4.6" + qs "^6.9.4" + readable-stream "^3.6.0" + semver "^7.3.2" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through2@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== + dependencies: + readable-stream "3" + +tildify@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" + integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== + +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + +titlecase@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/titlecase/-/titlecase-1.1.3.tgz#fc6d65ff582b0602410768ef1a09b70506313dc3" + integrity sha512-pQX4oiemzjBEELPqgK4WE+q0yhAqjp/yzusGtlSJsOuiDys0RQxggepYmo0BuegIDppYS3b3cpdegRwkpyN3hw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tslib@^2.0.3: + version "2.3.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + +underscore@^1.8.3: + version "1.13.1" + resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" + integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^3.0.0, uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +warehouse@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/warehouse/-/warehouse-5.0.1.tgz#4062c9c5d6c84f245532923a23ecb4b5441024a5" + integrity sha512-5BQEQP56bPY+cqocTho4syazuGgSoyKd0y3PsS2j8tGN10HH+CEfJSIY+KUw9D0k4jaVEFMXLz0KqCiUzTYb8A== + dependencies: + bluebird "^3.7.2" + cuid "^2.1.8" + graceful-fs "^4.2.10" + hexo-log "^4.0.1" + is-plain-object "^5.0.0" + jsonparse "^1.3.1" + rfdc "^1.3.0" + through2 "^4.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +window-size@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" + integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^5.2.2: + version "5.2.3" + resolved "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz#05541053414921bc29c63bee14b8b0dd50b07b3d" + integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA== + dependencies: + async-limiter "~1.0.0" + +y18n@^3.2.0: + version "3.2.2" + resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs@^3.10.0: + version "3.32.0" + resolved "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" + integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= + dependencies: + camelcase "^2.0.1" + cliui "^3.0.3" + decamelize "^1.1.1" + os-locale "^1.4.0" + string-width "^1.0.1" + window-size "^0.1.4" + y18n "^3.2.0"