居然真的滚挂了


起因

今天戳开博客发现只有首页能显示了, 点任意界面都会是空白, Chrome Dev里一看返回500

定位问题

上服务器看了看nginx的error log有报错:

PHP message: PHP Fatal error: Array and string offset access syntax with curly braces is no longer supported in /***/***/var/Json.php on line 32

一看就感觉是因为PHP更新了导致某些老的函数被删掉了, 于是检查PHP版本:

951901220761.png

果然, 我记得我装的时候是PHP7, 然后php-fpm已经被Arch滚到了8, 应该就是这个问题了.

尝试解决

首先肯定是想升级一下Typecho, 但是作者自从17年诈尸发布了一个版本以后就再也没有release过, 虽然github更新还蛮活跃的, 但是算了吧

于是就想还是降级PHP, 先看看源里有没有:

1846404239187.png

嗯 我觉得可以

于是先pacman -R php php-fpmpacman -S php7-fpm

需要注意的是原本的sock文件是/run/php-fpm/php-fpm.sock, 现在要改成/run/php-fpm7/php-fpm.sock

然后配置文件也从/etc/php/php.ini变成了/etc/php7/php.ini, 一定要打开extension=pdo_mysql才能访问数据库...

顺便发现以前写的telegram bot也挂了 原因是因为python大版本更新了 所以原来的pip安装的包也都没了...

总结

现在发现了滚动更新的另外一个坑就是某些重要基础软件包的大版本会直接更新...

然后你的服务可能因为大版本更新是非兼容性升级导致挂掉...

于是 还是docker大法好...什么时候把这个博客给docker化吧_(:3」∠)_(但是懒)


如何让Word公式编辑器自动使用斜体


只要不是需要特别奇怪的格式, Word自带的公式编辑器其实还挺好用的, 大部分的LaTeX公式指令也都支持, 但很烦的一点是这玩意默认样式和上下文是一样的, 而公式往往我们都希望它能够是斜体.
因此, 简单研究了一下以后, 发现可以通过VBA宏的形式来达到自动斜体公式的功能.

  1. 创建宏: 视图--录制宏, 宏名改成你喜欢的就行, 将宏指定到可以选择键盘, 然后输入你想绑定的快捷键, 这里我选择Alt+=也就是默认的插入公式快捷键
  2. 创建完以后它会自动开始录制, 点击界面下方状态栏的停止按钮即可停止录制
  3. 修改宏: 视图--查看宏, 选择你刚刚创建的宏名, 点击编辑, 然后在Sub ${宏名}()End Sub之间删除掉原有的代码然后插入如下代码:
    Selection.OMaths.Add Range:=Selection.Range 'Creates an equation
    Selection.OMaths(1).ConvertToMathText 'Converts an equation to math text

然后保存即可

  • 如果你之前设置了快捷键的话, 现在找到一处空白位置按下快捷键即可, 如果没有, 那么可以去选项中设置快捷键
  • 如果需要对代码进行调试, 可以在VBA的编辑窗口中进行单步调试

Ref: https://zhuanlan.zhihu.com/p/27072646


记一次简单的服务器修复


修复

  • 起因

今天戳开自己博客突然发现只显示出了Ngnix的默认错误界面, 而我挂在同一服务器上的seafile和gitea都没有问题, 就意识到大概是PHP挂了.

  • 定位问题

上服务器用ps aux | grep php一看, 果然是PHP没启动起来, 尝试重新启动发现并没有效果.
于是systemctl status php-fpm看看发生了什么, 第一行大概提示的是找不到mbstring.

20191206095800367_28116.png

一开始感觉只要把这个文件补上大概就可以了, 于是pacman -F mbstring看看.........没有
我有点不信邪...然后pacman -Fx ".*mbstring.*", 为啥mbstring.h的头文件都有但是没有mbstring.so啊不科学

网上查了查发现, Arch的PHP在打包的时候会加上--enable-mbstring参数将mbstring嵌入到PHP的可执行文件里面去, php -m也验证了这一点...
那你还提示个球啊摔..............

最后发现是, 我之前在php.ini里面加了句extension=mbstring, 然后PHP就会尝试去加载这个外部模块...emmmmmm

以及...其实...他就是只是报个错而已...对于正常启动没有任何影响

  • 重新定位问题

修改了php.ini以后, php-fpm.service还是启动不起来, 现在的报错变成了这样

20191206100425652_4564.png

怎么会没有权限呢? 我去看了一眼/run/php-fpm/php-fpm.sock的权限, root/group都是root, 然后看了看php-fpm.service的内容也没有指定用户和组, 那就应该是以root权限启动的啊

然后我顺手看了一眼php-fpm.conf.d/www.conf... 咦...有个pacnew文件, 是更新了啥吗? 然后我就懂了...

20191206100720401_5274.png)

所以 问题大概是PHP-FPM更新以后改进了权限运行策略...emmmmmm

于是 把这部分照着pacnew修改一下之后就好了= =

总结

滚动更新一大缺点就是...上游更新可能导致服务莫名其妙就被更挂了...

当然, 我的第一反应也是肯定更新了啥把php更挂了, 然后看了看官方的News啥也没提, 重新安装了几遍包也没啥用

........................嗯 好吧

顺带一提我的PHP-FPM版本是7.4.0-2

LeetCode刷题Wrapper


起因

LeetCode上的题目放到本地调试很麻烦, 需要在本地新建一个Main方法来运行, 多TestCase的测试也需要写很多重复代码, 所以想写一个简单Wrapper将很多重复的工作放在里面来执行.

目标

使用Wrapper之后, 只需要将Solution类放入再实现getTestCase()即可.
这里的Solution类可以直接不作任何修改的复制粘贴到LeetCode中, 不需要任何额外的代码即可直接运行这个类.

public class Problem7 extends AbstractSolution{
    /**
     * Given a 32-bit signed integer, reverse digits of an integer.
     * <p>
     * Example 1:
     * Input: 123
     * Output: 321
     * <p>
     * Example 2:
     * Input: -123
     * Output: -321
     * <p>
     * Example 3:
     * Input: 120
     * Output: 21
     * <p>
     * Note:
     * Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−231, 231 − 1]. For the purpose of this problem, assume that your function returns 0 when the reversed integer overflows.
     */
    class Solution{
        @Resource
        public int reverse(int x){
            // 统一调整到负数来处理
            boolean isPositive = x >= 0;
            x = isPositive ? -x : x;

            int result = 0;
            while(x < 0 && result >= Integer.MIN_VALUE / 10){
                int n = x % 10;
                if(result == 0){
                    result = result + n;
                }else{
                    if(result == Integer.MIN_VALUE / 10 && x < -8){
                        break;
                    }
                    result = result * 10 + n;
                }
                x /= 10;
            }
            result = x < 0 ? 0 : result;

            return isPositive ? -result : result;
        }
    }

    @Override
    protected Map<?, ?> getTestCase(){
        return ImmutableMap.builder()
                .put(123, 321)
                .put(-123, -321)
                .put(120, 21)
                .put(1534236469, 0)
                .put(-2147483412, -2143847412)
                .put(1463847412, 2147483641)
                .put(1234, 111)
                .build();
    }
}

由于使用Guava, 所以可能还需要引入它的依赖到工程中.
另外, Solution类的入口方法需要放上@Resource注解, 否则Wrapper无法找到运行的入口方法.
运行的结果如下所示:

TestCase 0: Passed
TestCase 1: Passed
TestCase 2: Passed
TestCase 3: Passed
TestCase 4: Passed
TestCase 5: Passed
TestCase 6: Wrong
    Input: 1234
    Output: 4321
    Expected: 111
Some TestCase Failed!

解析

首先展示一下AbstractSolution的内容:

public abstract class AbstractSolution{
    /**
     * 1. 实例化SolutionWrapper类
     * 2. 找到目标的Solution类并获得实例
     * 3. 找到目标方法(用@Resource注解)
     * 4. 检查参数类型和TestCase是否匹配
     * 5. 使用TestCase运行目标方法
     */
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException{
        // fetch solution wrapper
        AbstractSolution solutionWrapper = ReflectionUtils.getLoadedSubClass(AbstractSolution.class).newInstance();

        // find targetClass
        Class targetClass = null;
        Class<?>[] classes = solutionWrapper.getClass().getDeclaredClasses();
        for(Class<?> c : classes){
            if(c.getName().endsWith("$Solution")){
                targetClass = c;
            }
        }

        // get targetClass instance
        Object targetInstance = null;
        if(targetClass != null && targetClass.getDeclaredConstructors().length > 0){
            Constructor<?> targetConstructor = targetClass.getDeclaredConstructors()[0];
            targetInstance = targetConstructor.newInstance(solutionWrapper);
        }

        // find targetMethod
        Method targetMethod = null;
        if(targetClass != null){
            Method[] methods = targetClass.getDeclaredMethods();
            for(Method m : methods){
                if(m.isAnnotationPresent(Resource.class)){
                    targetMethod = m;
                }
            }
        }

        // check parameter and run
        Map<?, ?> testCase = solutionWrapper.getTestCase();
        if(targetMethod != null && targetMethod.getParameterTypes().length > 0 && testCase.size() > 0){
            Class<?> parameterTypes = targetMethod.getParameterTypes()[0];
            Class<?> testCaseType = testCase.keySet().toArray()[0].getClass();
            if(ReflectionUtils.isAssignableFrom(testCaseType, parameterTypes)){
                runCases(targetInstance, targetMethod, testCase);
            }
        }
    }

    private static boolean runCases(Object targetInstance, Method targetMethod, Map<?, ?> testCase) throws InvocationTargetException, IllegalAccessException{
        int index = 0;
        boolean allPassed = true;
        for(Object input : testCase.keySet()){
            Object result = targetMethod.invoke(targetInstance, input);
            Object expectedResult = testCase.get(input);
            boolean passed = result.toString().equals(expectedResult.toString());
            allPassed = passed && allPassed;

            if(passed){
                System.out.printf("TestCase %d: Passed\n", index++);
            }else{
                System.out.printf("TestCase %d: Wrong\n"
                                + "\tInput: %s\n"
                                + "\tOutput: %s\n"
                                + "\tExpected: %s\n",
                        index++, input, result, expectedResult);
            }
        }
        System.out.println(allPassed ? "All TestCase Passed!" : "Some TestCase Failed!");
        return allPassed;
    }

    protected abstract Map<?, ?> getTestCase();
}

我们在Problem7类中运行的main()方法放在了AbstractSolution里, 首先我们需要获得实际子类的实例, 即Problem7类的实例.
AbstractSolution类里, 我们是没办法知道到底具体是哪一个子类调用的main()方法的, 如果想要知道有两种方法:

  1. 运行的命令行参数里, 会带上实际执行的子类.
    例如在这个例子中, 命令行参数是java algorithm.leetcode.Problem7. 这样从命令行参数就可以知道实际调用main()的类是Problem7了.
  2. 在加载子类之前, 父类一定会被加载. 因此, 我们遍历当前一遍所有加载的类, 看当前类的哪一个子类被加载了, 就可以知道实际调用的类.

在这里, 我使用的是第二种方法, 通过ReflectionUtils.getLoadedSubClass(AbstractSolution.class)来获取, 具体实现如下:

    @SuppressWarnings("unchecked")
    public static <T> Class<? extends T> getLoadedSubClass(Class<T> superClass){
        // Read Loaded Classes
        Collection<Class<?>> classes = getLoadedClass(Thread.currentThread().getContextClassLoader());

        // Find the SubClass of CurrentClass
        for(Class c : classes){
            if(c.getSuperclass() != null && superClass.equals(c.getSuperclass())){
                return c;
            }
        }
        throw new IllegalStateException("No Valid SubClass.");
    }

    /**
     * 获取指定类加载器已加载的类
     */
    @SuppressWarnings("unchecked")
    public static Collection<Class<?>> getLoadedClass(ClassLoader cl){
        try{
            Field f = ClassLoader.class.getDeclaredField("classes");
            f.setAccessible(true);
            return (Collection<Class<?>>)f.get(cl);
        }catch(NoSuchFieldException | IllegalAccessException e){
            e.printStackTrace();
        }
        throw new IllegalStateException("Unexpected Error.");
    }

ClassLoader本身是没有提供方法让你获得当前已加载的类, 但是存在一个private的classes属性包含所有已加载的类, 因此可以通过读取该属性来获得指定类加载已加载的类.

在获得Problem7的实例solutionWrapper后, 我们再从Problem7类中找到其内部类$Solution并实例化它.
需要注意的是, 这里不能直接用newInstance(), 因为内部类没有public的构造器, 只有包含外部类引用的private构造器, 所以需要首先获得这个构造器, 然后使用构造器实例化内部类.

在获得内部类targetClass以后, 再遍历所有的方法找到标记有注解的方法作为入口方法targetMethod.
这里用@Resouce注解的原因只是...我觉得用JDK自带注解就够了...

最后, 检查一下testCase的类型和targetMethod的参数类型是否匹配, 就可以调用targetMethod来实际执行Solution类里的实际方法了.
这里考虑到存在primitive类型和其包装类混用的情况, 所以首先将其都unwrap, 如果都为primitive类型那么一定是可以转型成功的(当然可能损失精度).
如果没有primitive类型, 那么使用isAssignableFrom()方法来判断; 如果一个primitive一个正常对象, 那么必然转型失败.

    /**
     * 检查是否可从target转型到source
     * 考虑两个primitive类型一定可以互转(可能损失精度)
     */
    public static boolean isAssignableFrom(Class<?> source, Class<?> target){
        source = Primitives.unwrap(source);
        target = Primitives.unwrap(target);

        // All Object
        if(source.isPrimitive() && target.isPrimitive()){
            return true;
        }else if(!source.isPrimitive() && !target.isPrimitive()){
            return source.isAssignableFrom(target);
        }else{
            return false;
        }
    }

总的来说, 大体上的思路就是这样了, 我觉得还是很有意思的.

总结

这么一番操作下来, 感觉对框架内部实现的很多东西了解得更深了呢233
之前简单的看过Spring-Core的内部实现, 也是大量类似的检查代码, 以及一吨设计模式→_→
实话说, 感觉设计得有点太过复杂了...不过有可能是我看的源码版本比较老吧233(Spring 3.1.1)

懒果然是人类进步的阶梯(并不

Update 2019-05-10:
我把AbstractSolution又改了改, 在方法上做标注还是有一些略麻烦...
我把它改成了标注在内部类上, 然后用lookup属性来指定待执行的方法, 或者选取第一个找到的Public方法

public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException{
        // fetch solution wrapper
        AbstractSolution solutionWrapper = ReflectionUtils.getLoadedSubClass(AbstractSolution.class).newInstance();

        // find targetClass
        Class targetClass = null;
        Class<?>[] classes = solutionWrapper.getClass().getDeclaredClasses();
        for(Class<?> c : classes){
            if(c.isAnnotationPresent(Resource.class)){
                targetClass = c;
            }
        }

        // get targetClass instance
        Object targetInstance = null;
        if(targetClass != null && targetClass.getDeclaredConstructors().length > 0){
            Constructor<?> targetConstructor = targetClass.getDeclaredConstructors()[0];
            targetInstance = targetConstructor.newInstance(solutionWrapper);
        }

        // find targetMethod
        Method targetMethod = null;
        if(targetClass != null){
            Method[] methods = targetClass.getDeclaredMethods();
            Resource annotation = (Resource)targetClass.getAnnotation(Resource.class);
            for(Method m : methods){
                if(!annotation.lookup().isEmpty()){
                    // specify by lookup
                    if(m.getName().equals(annotation.lookup())){
                        targetMethod = m;
                    }
                }else{
                    // first public method
                    if(Modifier.isPublic(m.getModifiers())){
                        targetMethod = m;
                    }
                }
            }
        }

        // check parameter and run
        Map<?, ?> testCase = solutionWrapper.getTestCase();
        if(targetMethod != null && targetMethod.getParameterTypes().length > 0 && testCase.size() > 0){
            Class<?> parameterTypes = targetMethod.getParameterTypes()[0];
            Class<?> testCaseType = testCase.keySet().toArray()[0].getClass();
            if(ReflectionUtils.isAssignableFrom(testCaseType, parameterTypes)){
                runCases(targetInstance, targetMethod, testCase);
            }
        }
    }

Update 2019-09-04:

以前做的居然不支持多参数输入, 醉了醉了...
验证参数类型其实挺麻烦的, 感觉在我这个场景下也不是特别重要就删掉了...只检查参数数目是否一致
然后输出的检测考虑到可能会有比较复杂的类型输出出来干脆就用gson全部转换到字符串然后比较字符串好了...

public abstract class AbstractSolution{
    private final static Gson gson = new GsonBuilder().create();

    /**
     * 1. 实例化SolutionWrapper类
     * 2. 找到目标的Solution类并获得实例
     * 3. 找到目标方法(用@Resource注解)
     * 4. 检查参数类型和TestCase是否匹配
     * 5. 使用TestCase运行目标方法
     */
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException{
        // fetch solution wrapper
        AbstractSolution solutionWrapper = ReflectionUtils.getLoadedSubClass(AbstractSolution.class).newInstance();

        // find targetClass
        Class targetClass = null;
        Class<?>[] classes = solutionWrapper.getClass().getDeclaredClasses();
        for (Class<?> c : classes) {
            if (c.isAnnotationPresent(Resource.class)) {
                targetClass = c;
            }
        }

        // get targetClass instance
        Object targetInstance = null;
        if (targetClass != null && targetClass.getDeclaredConstructors().length > 0) {
            Constructor<?> targetConstructor = targetClass.getDeclaredConstructors()[0];
            targetInstance = targetConstructor.newInstance(solutionWrapper);
        }

        // find targetMethod
        Method targetMethod = null;
        if (targetClass != null) {
            Method[] methods = targetClass.getDeclaredMethods();
            Resource annotation = (Resource)targetClass.getAnnotation(Resource.class);
            for (Method m : methods) {
                if (!annotation.lookup().isEmpty()) {
                    // specify by lookup
                    if (m.getName().equals(annotation.lookup())) {
                        targetMethod = m;
                    }
                } else {
                    // first public method
                    if (Modifier.isPublic(m.getModifiers())) {
                        targetMethod = m;
                    }
                }
            }
        }

        // check parameter and run
        Map<?, ?> testCase = solutionWrapper.getTestCase();
        if (targetMethod != null && targetMethod.getParameterTypes().length > 0 && testCase.size() > 0) {
            runCases(targetInstance, targetMethod, testCase);
        }
    }

    private static boolean runCases(Object targetInstance, Method targetMethod, Map<?, ?> testCase) throws InvocationTargetException, IllegalAccessException{
        int index = 0;
        boolean allPassed = true;
        for (Object input : testCase.keySet()) {
            // read params
            Object[] params;
            if (targetMethod.getParameterCount() > 1) {
                params = new Object[Array.getLength(input)];
                for (int i = 0; i < params.length; i++) {
                    params[i] = Array.get(input, i);
                }
            } else {
                params = new Object[]{input};
            }

            // check parameter length
            if (params.length != targetMethod.getParameterCount()) {
                throw new IllegalArgumentException("Incompatible Parameters.");
            }

            // actual invoke
            Object result = targetMethod.invoke(targetInstance, params);
            Object expectedResult = testCase.get(input);

            // compare string
            String inputStr = gson.toJson(input);
            String resultStr = gson.toJson(result);
            String expectedStr = gson.toJson(expectedResult);
            boolean passed = resultStr.equals(expectedStr);
            allPassed = passed && allPassed;

            if (passed) {
                System.out.printf("TestCase %d: Passed\n", index++);
            } else {
                System.out.printf("TestCase %d: Wrong\n"
                                + "\tInput: %s\n"
                                + "\tOutput: %s\n"
                                + "\tExpected: %s\n",
                        index++, inputStr, resultStr, expectedStr);
            }
        }
        System.out.println(allPassed ? "All TestCase Passed!" : "Some TestCase Failed!");
        return allPassed;
    }

    protected abstract Map<?, ?> getTestCase();
}

关于键盘的一些碎碎念


像我可能是对键盘不太挑的类型, 一直薄膜键盘用下来感觉也挺不错的.

其实也用过同学的机械, 无论是国产轴还是原厂轴, 亦或是青轴, 茶轴, 黑轴, 红轴都试过, 但始终觉得都不是我的菜.

依稀记得买的第一个键盘是Fuhlen L400, 39块钱买了不吃亏不上当, 其实我用起来一直感觉还是不错的, 直到最近发现shift键经常按下去谈不起来emmm

后来买了笔记本, 型号是Gigabyte U2442D, 这是一款每次拿出来总有人要问是什么牌子的笔记本2333

当然这时候我一般会装一个x: 这是全球第二大主板厂商(x

言归正传, 我觉得这款笔记本的键盘的使用体验还是相当不错的, 和同期mbp2013款的键盘使用体验不相上下.
也是就此, 我觉得自己其实喜欢上这种短键程的剪刀脚键盘, 特别是悬浮式巧克力键帽.
我可以很自然地把手掌放在桌子上, 然后手指在键盘上翻飞, 非常舒适. 但是像那种一般的高键帽键盘就...必须得悬空打字了, 实话说我觉得很不习惯

新款mbp的蝴蝶键盘就真的是...有一种强烈的按锅仔片的感觉

于是, 后来台式机买新键盘的时候, 我就买了Rapoo E9100P, 虽然作为一个薄膜键盘来说确实有些贵了, 但是不得不说使用体验还是很棒的, 比如说我现在就是在用这块键盘敲字www
其他的薄膜键盘的推荐可能是thinkpad那款了吧...不过高昂的价格让我望而却步Orz

最后就是最近, 因为之前说Fuhlen L400挂了嘛, 考虑到Rapoo E9100P给我带来的良好体验, 于是入手了一块Rapoo V500.
本来一开始打算再买一块Rapoo E9100P这个系列的, 但是还是感觉有些贵= =
而且考虑到毕业了, 估计也不会再要, 不如干脆买一个超入门机械好了→_→ 顺便也可以习惯一下悬空打字的感觉吧emmm

未来工作的话主力键盘, 如果能够习惯机械的感觉的话, 可能会考虑换一块Varmilo吧...
应该会买87键版的, 然后海韵蓝? 主要是太好看了啊啊啊啊啊啊啊啊(我就是颜狗)
应该会买无线版本吧...嗯

20190505002313983_14909.png
20190505002418824_5345.png


Update 2019-05-24:
用了一段时间以后发现Rapoo V500简直坑爆了, 手感什么的就不说了, 我倒要求不是那么高
但主要是有些组合键按不下去啊啊啊啊啊
一开始看到常用键无冲就没想那么多, 直到最近用sublime的时候发现命令面板Ctrl+Alt+P老是按不出来, 于是...测试了一下
QQ截图20190524083653.png
好吧...就是这垃圾键盘按不出来, 而且不只是Ctrl+Alt+P还有0/U/K/Enter也是...
emmmmmmmmmmmmmmmmmmm...........我那个40块的Fuhlen L400都不会这样啊摔
果然机械键盘一定要注意全键无冲么Orz