作者归档:RAE

git 常用命令

  • git 设置提交用户
git config user.name "yourname"
  • git 保存用户密码
git config credential.helper store
  • git 查看配置

git config -l

vscode 配置文件

vs code 开发常用配置文件

{
    "editor.fontFamily": "Ubuntu Mono",
    "editor.fontSize": 16,
    "editor.lineHeight": 30,
    "explorer.confirmDelete": false,
    // Default formatter for <template> region
    "git.enableSmartCommit": true,
    "editor.quickSuggestions": {
        "strings": true
    },
    "git.autofetch": true,
    "workbench.colorTheme": "One Dark Pro",
    "element-helper.version": "2.4",
    "editor.suggestSelection": "first",
    "vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue",
    "explorer.confirmDragAndDrop": false,
    // vscode默认启用了根据文件类型自动设置tabsize的选项
    "editor.detectIndentation": false,
    "files.autoSave": "off",
    // 添加 vue 支持
    // "eslint.run": "onSave",
    //  #去掉代码结尾的分号
    "prettier.semi": false,
    //  #使用带引号替代双引号
    "prettier.singleQuote": true,
    // // #这个按用户自身习惯选择
    "vetur.format.defaultFormatter.html": "js-beautify-html",
    // //  #让函数(名)和后面的括号之间加个空格
    "javascript.format.insertSpaceBeforeFunctionParenthesis": false,
    "typescript.format.insertSpaceBeforeFunctionParenthesis": false,
    "vetur.format.defaultFormatterOptions": {
        "js-beautify-html": {
            "wrap_attributes": "force-aligned"
            // #vue组件中html代码格式化样式
        }
    },
    "[javascript]": {
        "editor.defaultFormatter": "vscode.typescript-language-features"
    },
    "window.zoomLevel": 0,
    "terminal.integrated.fontSize": 16,
    "[vue]": {
        "editor.defaultFormatter": "octref.vetur"
    },
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": false
    },
    "[json]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[html]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "files.exclude": {
        "**/.classpath": true,
        "**/.project": true,
        "**/.settings": true,
        "**/.factorypath": true
    },
    "workbench.sideBar.location": "left",
    "editor.formatOnSave": true,
    "vetur.format.defaultFormatter.js": "vscode-typescript",
    "eslint.codeActionsOnSave.mode": "problems",
    // "eslint.enable": false,
    "eslint.format.enable": true,
    "workbench.iconTheme": "material-icon-theme",
    "[jsonc]": {
        "editor.defaultFormatter": "vscode.json-language-features"
    },
    "editor.renameOnType": true,
    "vsicons.dontShowNewVersionMessage": true,
    "workbench.statusBar.visible": false,
    "workbench.activityBar.visible": true
}

Android中使用动态代理巧妙的管理SharedPreferences配置项

在Android应用程序中不少地方会使用SharedPreferences来保存配置文件,这样你就会出现不少下面的写法:


// 程序配置文件
public class MyAppConfig {
    SharedPreferences mSharedPreferences;

    public MyAppConfig(Context context) {
        mSharedPreferences = context.getSharedPreferences("AppConfig", Context.MODE_PRIVATE);
    }

    /**
     * 设置配置1
     */
    public void setConfig1(String value) {
        mSharedPreferences.edit().putString("config1", value).apply();
    }

    /**
     * 获取配置1
     */
    public String getConfig1() {
        return mSharedPreferences.getString("config1", "");
    }

    // ... 省略其他更多的配置项
}

这样一来我们就需要写很多跟SharedPreferences打交道的代码,其实我们关注的只有SET方法GET方法两个方法罢了。那有没有办法,我只需要定义好配置的接口就直接获取到值呢?这就是本篇文章要讨论的啦。其实我们理想效果应该是这样的:


// 定义一个配置接口
public interface ITestConfig {
    // 定义获取配置的Get、Set方法
    void setName(String name);
    String getName();
}

// 实际操作中的调用
private void runTestMethod() {
    // 能实现这样的效果就完美了
    ITestConfig config = XXX.create(context, ITestConfig.class);
    Log.i("rae", "结果:" + config.getName());
}

探索

不妨思考一下,这种需求好像我们在哪个地方见过呢?没错,就是我们经常用到的Retrofit框架中就有用到,我能对public <T> T create(final Class<T> service){}这个方法应该很熟悉了,我们好奇它为什么传入一个接口它就能构造出接口的实例呢?让我们走进源码看看:

 public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }

    // 关键的地方来了:Proxy,动态代理!
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          private final Object[] emptyArgs = new Object[0];

          @Override public @Nullable Object invoke(Object proxy, Method method,
              @Nullable Object[] args) throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }

就是这个神奇的动态代理。具体什么是动态代理这里就不做解析了,有兴趣的同学可以看看这里介绍的Java动态代理

动手前操作

知道大概的原理之后,我们就开始动手操作了。理一理咱们的思路,定义一个动态代理实现以下功能:

  • 处理我们定义的Get、Set方法,利用SharedPreferences保存配置项
  • 还可以处理一个clear()方法清除配置
  • 还可以处理一个remove(String key) 方法移除配置项
  • 还要处理保存对象类型(利用json字符串来实现)

动态代理的类


/**
 * 应用程序配置注解
 * Created by rae on 2020-02-20.
 * Copyright (c) https://github.com/raedev All rights reserved.
 */
@Documented
@Retention(RUNTIME)
public @interface Config {

    /**
     * 程序配置名称
     */
    String value();

}

/**
 * 应用程序代理类
 * Created by rae on 2020-02-20.
 * Copyright (c) https://github.com/raedev All rights reserved.
 */
public final class AppConfigHandler {

    private AppConfigHandler() {
    }

    /**
     * 创建程序配置代理类
     *
     * @param cls 类的Class
     */
    @SuppressWarnings("unchecked")
    public static <T> T create(Context context, Class<T> cls) {
        Config config = cls.getAnnotation(Config.class);
        if (config == null) {
            throw new RuntimeException("请在配置类标注@Config()");
        }
        if (!cls.isInterface()) {
            throw new RuntimeException("配置类必须是接口");
        }
        String configName = config.value();
        if (TextUtils.isEmpty(configName)) {
            configName = cls.getName();
        }
        SharedPreferences preferences = context.getSharedPreferences(configName, Context.MODE_PRIVATE);
        // 创建动态代理
        return (T) Proxy.newProxyInstance(cls.getClassLoader(), new Class<?>[]{cls}, new ConfigProxy(preferences));
    }

    private static class ConfigProxy implements InvocationHandler {

        private final SharedPreferences mPreference;
        private final Gson mGson = new Gson();

        private ConfigProxy(SharedPreferences preference) {
            this.mPreference = preference;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            String methodName = method.getName().toUpperCase();
            // 清除配置文件
            if (methodName.equalsIgnoreCase("clear")) {
                mPreference.edit().clear().apply();
            }
            // 移除配置项处理
            else if (methodName.equalsIgnoreCase("remove") && args != null) {
                String key = args[0].toString().toUpperCase();
                mPreference.edit().remove(key).apply();
            }
            // Get方法处理
            else if (methodName.startsWith("SET")) {
                setValue(methodName.replace("SET", ""), method, args);
            }
            // Set方法处理
            else if (methodName.startsWith("GET")) {
                return getValue(methodName.replace("GET", ""), method, args);
            }
            // Is方法处理,比如:isLogin()、isVip(),这类的布尔值
            else if (methodName.startsWith("IS")) {
                boolean value = mPreference.getBoolean(methodName.replace("IS", ""), false);
                return value;
            }
            return null;
        }

        /**
         * 设置配置值
         */
        private void setValue(String name, Method method, Object[] args) {
            if (args.length != 1) throw new IllegalArgumentException("set方法的方法参数只允许一个");
            Class<?>[] parameterTypes = method.getParameterTypes();
            Class<?> parameterType = parameterTypes[0];
            Object arg = args[0];
            SharedPreferences.Editor editor = mPreference.edit();
            if (parameterType == String.class) {
                editor.putString(name, (String) arg);
            } else if (parameterType == int.class) {
                editor.putInt(name, (int) arg);
            } else if (parameterType == boolean.class) {
                editor.putBoolean(name, (boolean) arg);
            } else if (parameterType == float.class) {
                editor.putFloat(name, (float) arg);
            } else if (parameterType == long.class) {
                editor.putLong(name, (long) arg);
            } else {
                // 其他值默认使用Json字符串
                String json = mGson.toJson(arg);
                editor.putString(name, json);
            }
            editor.apply();
        }

        /**
         * 获取配置值
         */
        private Object getValue(String name, Method method, Object[] args) {
            Class<?> type = method.getReturnType();
            Object defaultValue = args == null ? null : args[0];
            if (type == String.class) {
                return mPreference.getString(name, (String) defaultValue);
            } else if (type == int.class) {
                return mPreference.getInt(name, defaultValue == null ? 0 : (int) defaultValue);
            } else if (type == boolean.class) {
                return mPreference.getBoolean(name, defaultValue != null && (boolean) defaultValue);
            } else if (type == float.class) {
                return mPreference.getFloat(name, defaultValue == null ? 0 : (float) defaultValue);
            } else if (type == long.class) {
                return mPreference.getLong(name, defaultValue == null ? 0 : (long) defaultValue);
            } else {
                // 其他值默认使用Json字符串
                String json = mPreference.getString(name, null);
                return mGson.fromJson(json, type);
            }
        }
    }
}

实践操作

最终我们定义一个接口,就可以轻松实现读取配置文件啦!

/**
 * 程序配置
 * Created by rae on 2020/2/22.
 * Copyright (c) https://github.com/raedev All rights reserved.
 */
@Config("YourAppConfig")
public interface IAppConfig {

    void setUserName(String name);

    String getUserName(String defaultValue);

    void setVip(boolean isVip);

    boolean isVip();

    void setVersion(int version);

    int getVersion();

    void clear();

    void remove(String key);
}

方法调用

private void runTestMethod() {
    IAppConfig config = AppConfigHandler.create(getApplicationContext(), IAppConfig.class);
    config.clear();
    config.setUserName("RAE");
    config.remove("UserName");
    Log.i("Rae", "username is " + config.getUserName("DefaultValue"));
    config.setVip(true);
    Log.i("Rae", "is vip: " + config.isVip());
    config.setVersion(10);
    Log.i("Rae", "version is  " + config.getVersion());
}

输出结果

I/Rae: username is DefaultValue
I/Rae: is vip: true
I/Rae: version is  10

docker 安装jenkins容器

docker 中安装Jenkins很方便,而且不会污染服务器的环境,采用docker是再适合不过了。

安装Jenkins镜像

# 这里安装官方镜像
docker pull docker.io/jenkins

创建docker file

mkdir -d /home/docker/jenkins
cd /home/docker
vim jenkins.yml

jenkins.yml 文件内容如下:

version: '3'
services:
    nexus:
        hostname: jenkins
        container_name: jenkins
        image: docker.io/jenkins
        restart: always
        ports:
            - "8080:8080"
        volumes:
            - /home/docker/jenkins/:/jenkins-data

启动容器

如果没有安装docker-compose的先安装该命令

# 安装docker-compose
yum install docker-compose

通过docker file 创建和运行容器,-d 表示后台运行

# 启动
docker-compose --file jenkins.yml  up -d
# 查看
docker ps

正常启动后就能访问,在浏览器输入: http://127.0.0.1:8080 访问。

镜像操作

docker pull redis:1.0 – 安装镜像,其中1.0为版本号,不填写默认是最新的。

docker start containerId 启动容器

docker exec -it containerId /bin/bash 进入容器

redis

安装容器:

docker run -p 6379:6379 -v $PWD/data:/data -d redis redis-server --appendonly yes

进入容器:

docker exec -it 71352213d7d6 redis-cli

mysql

安装容器:

docker pull mysql

sudo docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=raedev@qq.com -d mysql containerId

docker 安装容器以及常用操作命令

镜像操作


# 安装镜像,其中1.0为版本号,不填写默认是最新的。
docker pull redis:1.0

# 启动容器
docker start containerId

# 进入容器
docker exec -it containerId /bin/bash

# 以root 用户进入容器
docker exec -u root -it yourconainer /bin/bash

安装redis容器


# 安装容器:
docker run -p 6379:6379 -v $PWD/data:/data  -d redis redis-server --appendonly yes

# 进入容器
docker exec -it 71352213d7d6 redis-cli

安装mysql容器


# 安装容器
docker pull mysql

# 运行
sudo docker run --name mysql -p 3306:3306 -e MYSQL\_ROOT\_PASSWORD=raedev@qq.com -d mysql containerId

centos 常用命令总结

防火墙

开启防火墙

systemctl start firewalld

开启端口号

firewall-cmd --zone=public --add-port=80/tcp --permanent

  • –zone #作用域
  • –add-port=80/tcp #添加端口,格式为:端口/通讯协议
  • –permanent #永久生效,没有此参数重启后失效

查看端口号

firewall-cmd --list-ports

重启防火墙

firewall-cmd --reload

文件操作

删除文件夹

rm -rf youdir

解压ZIP

yum install zip unzip

unzip filename

SSH 复制文件

scp LocalFile UserName@RemoteIP:RemoteFile

配置虚拟内存

参考文章

  1. 添加swap文件大小为2G
    默认情况下, of=/swapfile 即swapfile文件创建在/var/目录下。
    若我在创建在/opt/image/目录下, 则下面所有的操作里有/swapfile的都要改为/opt/image/swap
    dd if=/dev/zero of=/swapfile bs=1k count=2048000
  2. 创建SWAP文件
    mkswap /swapfile
  3. 激活SWAP文件
    swapon /swapfile
  4. 查看SWAP信息是否正确
    swapon -s
  5. 添加到fstab文件中让系统引导时自动启动
    注意, 这里是采用了swapfile文件的默认路径, 即/var/swapfile。若你上面的操作中swapfile文件不是在/var/目录下, 则下面的/var/swapfile也要相应修改为自己设写的。
    echo "/var/swapfile swap swap defaults 0 0" >> /etc/fstab
  6. 用命令free检查2G交换分区生效
    free -m
    或者, 检查meminfo文件
    grep SwapTotal /proc/meminfo
  7. 释放SWAP文件
    swapoff /swapfile
  8. 删除SWAP文件
    rm -fr /swapfile

RAE 原创文章,转载请注明出处

webpack 加密压缩 js文件

UglifyJS Webpack Plugin插件用来缩小(压缩优化)js文件

单个文件压缩

# 命令:webpack [js文件]
# 例如打包app.js
webpack app.js

如果是多个文件就采用webpack.config.js文件来配置。

基础配置 webpack.config.js文件

const path = require("path");
const UglifyJsPlugin = require('uglifyjs-3-webpack-plugin');
module.exports = {
    mode: "production",
    entry: {
        common: path.resolve(__dirname, './template/src/common.js') // 入口文件
    },
    output: {
        path: path.resolve(__dirname, './dist/'), // 打包目录
        filename: '[name].min.js', // 输出文件名
        chunkFilename: '[name].min.js' // commonChunk 输出文件
    },
    optimization: {
        minimizer: [new UglifyJsPlugin()]
    }
}

composer 设置阿里云镜像源

composer 更换到阿里云镜像源快到飞起。官网地址

全局配置(推荐)

所有项目都会使用该镜像地址:

composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

取消配置:

composer config -g --unset repos.packagist

项目配置

仅修改当前工程配置,仅当前工程可使用该镜像地址:

composer config repo.packagist composer https://mirrors.aliyun.com/composer/

取消配置:

composer config --unset repos.packagist

调试

composer 命令增加 -vvv 可输出详细的信息,命令如下:

composer -vvv require alibabacloud/sdk

遇到问题?

  1. 建议先将Composer版本升级到最新:
    composer self-update
  2. 执行诊断命令:
    composer diagnose
  3. 清除缓存:
    composer clear
  4. 若项目之前已通过其他源安装,则需要更新 composer.lock 文件,执行命令:
    composer update --lock

Macbook HomeBrew 相关操作总结

一、安装Brew

# 安装过程会非常慢
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
# 如果无法访问,使用国内镜像
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
# 更新
brew update
# 卸载
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/HomebrewUninstall.sh)"

二、更换镜像源

更换镜像源可以解决 brew 下载慢的问题。

阿里云镜像站

git -C "$(brew --repo)" remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git
git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.aliyun.com/homebrew/homebrew-core.git

清华大学开源软件镜像站 官方链接

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
git -C "$(brew --repo homebrew/cask-fonts)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-fonts.git
git -C "$(brew --repo homebrew/cask-drivers)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-drivers.git

更新 homebrew-bottles

根据自己的终端选择

1、bash 终端

echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles' >> ~/.bash_profile
source ~/.bash_profile

2、zsh 终端

echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles' >> ~/.zshrc
source ~/.zshrc

复原

# brew 程序本身,Homebrew/Linuxbrew 相同
git -C "$(brew --repo)" remote set-url origin https://github.com/Homebrew/brew.git

# 以下针对 mac OS 系统上的 Homebrew
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
git -C "$(brew --repo homebrew/cask-fonts)" remote set-url origin https://github.com/Homebrew/homebrew-cask-fonts.git
git -C "$(brew --repo homebrew/cask-drivers)" remote set-url origin https://github.com/Homebrew/homebrew-cask-drivers.git

# 以下针对 Linux 系统上的 Linuxbrew
git -C "$(brew --repo homebrew/core)" remote set-url origin https://github.com/Homebrew/linuxbrew-core.git

# 更换后测试工作是否正常
brew update

三、解决brew 一直卡 Updating Homebrew

方法一:关闭brew每次执行命令时的自动更新(推荐)

vim ~/.bash_profile
export HOMEBREW_NO_AUTO_UPDATE=true

方法二:出现Updating Homebrew的时候ctrl+c一下就行

mysql 备份数据库常用命令

1、备份命令

格式:mysqldump -h主机名 -P端口 -u用户名 -p密码 –database 数据库名 > 文件名.sql

例如: mysqldump -h 192.168.1.100 -p 3306 -uroot -ppassword –database cmdb > /data/backup/cmdb.sql

2、备份压缩

导出的数据有可能比较大,不好备份到远程,这时候就需要进行压缩

格式:mysqldump -h主机名 -P端口 -u用户名 -p密码 –database 数据库名 | gzip > 文件名.sql.gz

例如: mysqldump -h192.168.1.100 -p 3306 -uroot -ppassword –database cmdb | gzip > /data/backup/cmdb.sql.gz

3、备份同个库多个表

格式:mysqldump -h主机名 -P端口 -u用户名 -p密码 –database 数据库名 表1 表2 …. > 文件名.sql

例如 mysqldump -h192.168.1.100 -p3306 -uroot -ppassword cmdb t1 t2 > /data/backup/cmdb_t1_t2.sql

4、同时备份多个库

格式:mysqldump -h主机名 -P端口 -u用户名 -p密码 –databases 数据库名1 数据库名2 数据库名3 > 文件名.sql

例如:mysqldump -h192.168.1.100 -uroot -ppassword –databases cmdb bbs blog > /data/backup/mutil_db.sql

5、备份实例上所有的数据库

格式:mysqldump -h主机名 -P端口 -u用户名 -p密码 –all-databases > 文件名.sql

例如:mysqldump -h192.168.1.100 -p3306 -uroot -ppassword –all-databases > /data/backup/all_db.sql

6、备份数据出带删除数据库或者表的sql备份

格式:mysqldump -h主机名 -P端口 -u用户名 -p密码 –add-drop-table –add-drop-database 数据库名 > 文件名.sql

例如:mysqldump -uroot -ppassword –add-drop-table –add-drop-database cmdb > /data/backup/all_db.sql

7、备份数据库结构,不备份数据

格式:mysqldump -h主机名 -P端口 -u用户名 -p密码 –no-data 数据库名1 数据库名2 数据库名3 > 文件名.sql

例如:mysqldump –no-data –databases db1 db2 cmdb > /data/backup/structure.sql