独书先生 Menu

All items for 2月, 2022

李佳琪直播间抢到了娇兰金钻粉底液!

背景

小编的媳妇常年关注李佳琪直播间,所以经常会剁手一些护肤品和化妆品,特别是现在比较火爆流行的化妆品,而且越是火爆的东西越难抢。

前几天就抢到了娇兰金钻粉底液,一个大瓶30ml和6个小样每个5ml。快递到手后立马试用了下,说是感觉效果很不错。

媳妇说,她平时化妆品用的不多,这个化妆品一次买的量又比较大,可以用很久。所以想是不是可以把小样出给集美们,让不想一次买那么多的集美们也能方便的试用一下,试用好的话就可以继续去官网买大瓶的,试用感觉不好的话,反正买的不多也不亏。

福利

我们看下官网的价格,750一个30ml的大瓶,价格已经劝退了没有用过的集美了

这个价格折算一个小样5ml的125一个,可见这个粉底液真的很受欢迎。媳妇说这个价值125的小样,打折给关注咱们公众号的朋友100包邮的价格出。

有朋友注意到,小红书和闲鱼上也有很多更便宜的卖这些护肤品的商家,大家一定要注意甄别,不是说价格便宜的一定不好,而是你想想为啥官网125价值的粉底液,为啥卖你半价不到,而且还能卖那么多?关键脸蛋是自己的,现在网上的假货实在太多了,大家一定要注意。

有意向的小伙伴,关注公众号:技术分社,后台回复:小编,即可添加微信咨询。

微信openHacking

总结

因为这个粉底液实在太抢手了,估计能很快出完。出完了也没有关系,之后抢到了其他好用的护肤品或者化妆品,我们还会考虑出一些给需要的小伙伴。

你可以先关注微信公众号或者直接添加文章底部的微信,有新的东西,我们再第一时间更新公众号和朋友圈。
谢谢大家的支持。

js toFixed精度问题兼容方法

原生的js的toFixed有精确度问题,需要兼容.

问题:

1.35.toFixed(1) // 1.4 正确
1.335.toFixed(2) // 1.33 错误
1.3335.toFixed(3) // 1.333 错误
1.33335.toFixed(4) // 1.3334 正确
1.333335.toFixed(5) // 1.33333 错误
1.3333335.toFixed(6) // 1.333333 错误

解决方法:

// toFixed兼容方法
Number.prototype.toFixed = function (n) {
    if (n > 20 || n < 0) {
        throw new RangeError('toFixed() digits argument must be between 0 and 20');
    }
    const number = this;
    if (isNaN(number) || number >= Math.pow(10, 21)) {
        return number.toString();
    }
    if (typeof (n) == 'undefined' || n == 0) {
        return (Math.round(number)).toString();
    }

    let result = number.toString();
    const arr = result.split('.');

    // 整数的情况
    if (arr.length < 2) {
        result += '.';
        for (let i = 0; i < n; i += 1) {
            result += '0';
        }
        return result;
    }

    const integer = arr[0];
    const decimal = arr[1];
    if (decimal.length == n) {
        return result;
    }
    if (decimal.length < n) {
        for (let i = 0; i < n - decimal.length; i += 1) {
            result += '0';
        }
        return result;
    }
    result = integer + '.' + decimal.substr(0, n);
    const last = decimal.substr(n, 1);

    // 四舍五入,转换为整数再处理,避免浮点数精度的损失
    if (parseInt(last, 10) >= 5) {
        const x = Math.pow(10, n);
        result = (Math.round((parseFloat(result) * x)) + 1) / x;
        result = result.toFixed(n);
    }

    return result;
};

参考自:https://www.jianshu.com/p/849b0ae36b36

CentOS 创建新用户并赋予 root 权限,禁止 root 登陆

原文:https://lwebapp.com/zh/post/centos-adduser

需求

最近遇到了阿里云 ECS 服务器被 DDoS 攻击的问题,说明我们的服务器安全性有待提高。从 ssh 登陆方面考虑,可以给 linux 系统设置子用户,禁止 root 登陆,能够提升一定的安全性。

本文你将会学习到

  • 如何给 linux 系统、CentOS 系统创建新用户,并设置密码
  • 如何给 CentOS 新用户授予权限控制
  • 如何禁止 root 登陆提升服务器 ssh 远程连接安全
  • 如何重启 sshd 服务
  • 如何查看文件权限及修改文件权限

用户管理

  1. 创建用户,设置密码

    先创建一个用户,起个名字,比如 lwebapp

    adduser lwebapp
    

    为用户 lwebapp 设置密码,会触发交互,输入密码即可

    passwd lwebapp # 触发交互,输入密码 lwebappwd
    
  2. 授予 root 权限
    修改sudoers,为新创建的用户赋予 root 权限,这样每次只需要用新用户lwebapp登录,就能完成所有服务器操作了。

    sudoers文件在/etc目录下,首先修改下这个文件的权限为允许修改

    chmod 777 /etc/sudoers
    

    然后用vim打开

    敲击i进入编辑模式

    vim /etc/sudoers
    

    接着在root ALL=(ALL) ALL这句话后面换行,输入

    lwebapp ALL=(ALL) ALL # 为lwebapp赋予所有权限,和上面的root一样
    

    敲击Esc,输入冒号:进入 vim 命令模式,再输入wq,敲击Enter保存退出

    保存完文件再把文件权限设置回来

    chmod 444 /etc/sudoers
    
  3. 禁止 root 登入
    因为有了新用户,就把 root 用户登陆的权限给禁止掉,这样黑客就没办法通过破解 root 登陆才操作服务器,至少我们的新用户名变了,给黑客攻击增加了一层难度

    找到并编辑sshd_config文件

    vim /etc/ssh/sshd_config
    

    找到PermitRootLogin yes,把yes改成no,意思就是不允许 root 账户登陆

    PermitRootLogin no
    
  4. 重启 sshd
    最后重启 sshd 才能生效

    systemctl restart sshd.service
    

原文:https://lwebapp.com/zh/post/centos-adduser

扩展学习

文件权限

  1. 查看文件权限
    stat -c '%A %a %n' *
    
  2. 让某个文件夹内所有文件都有 777 权限
    chmod 777 -R ./webapps
    

vim 基本操作

  1. 打开一个文件
    vim file.txt
    
  2. 进入编辑模式
    敲击 i,终端界面底部显示-- INSERT --即为编辑模式

  3. 进入命令模式
    输入:,终端界面底部显示:和光标

  4. 退出编辑模式或者命令模式
    敲击Esc

  5. 保存退出
    命令模式下,输入wq,敲击确定Enter即完成保存退出

  6. 强制退出
    命令模式下,输入q!,敲击确定Enter即完成强制退出

sshd 服务

  1. 查看 sshd 服务状态

    systemctl status sshd.service
    

    会显示一系列服务状态,比如running就表示是启动成功的状态

  2. 启动 sshd 服务

    systemctl start sshd.service
    
  3. 重启 sshd 服务
    systemctl restart sshd.service
    
  4. 设置为开机启动
    systemctl enable sshd.service
    

参考

Vmware Workstation16 出现 “VMware Workstation 不可恢复错误:(vcpu-1)”故障解决

原文:https://lwebapp.com/zh/post/vmware-vcpu-error

问题

小编最近准备在 Windows 电脑上安装 VMware Workstation 虚拟机,然后安装 macOS 系统用来做苹果系统环境的测试。

在安装 VMware 后,启动 macOS 虚拟机的时候,碰到了vcpu-0vcpu-1 报错,具体的报错关键信息有

VMware Workstation不可恢复错误:(vcpu-1)

Exception 0xc0000005 (access violation) has occurred.

还有

VMware Workstation 不可恢复错误: (vcpu-0)

在网上搜索了一些解决方案,试了好几种方案之后,又遇到了新的问题,macOS 虚拟机在启动的时候,会一直无限循环重启(注意,不是我的 Windows 宿主机系统重启,宿主机 Windows 未显示任何异常,VMware 也没有报错)。

上面的两个问题,小编尝试了以下列举的几个方法,成功解决问题。但是不知道具体是哪一个配置起了作用,遇到同样问题的朋友可以依次尝试。

解决

解决方案 1

进入 BIOS 将虚拟化技术 Intel Virtual Technology 设置,由Disable禁用设置为Enable可用.

具体设置步骤:

  1. 重启电脑或者重新开机,在成功开机之前,不停地点按F1键,即可进入 BIOS 界面(不同牌子的电脑不一样,F1 不行的话,再重启试试 F2 或者 F5、F8、Delete 都有可能,具体信息可以参考下自己品牌电脑官网说明)
  2. 进入 BIOS 后找到菜单 Advanced,进入 cpu configuration
  3. 将 intel virtual technology 设置为 Enable,然后保存退出;
  4. 成功开机后,重新打开虚拟机即可

一些品牌电脑 BIOS 系统中的开启虚拟化技术的位置

  • 某些 HP(惠普)电脑进入 BIOS 后
    1. 需要选择 System Configuration(系统配置)菜单
    2. 然后选择 Device Configuration(设备配置)
    3. 找到 Virtualization Technology,设置为 Enabled
  • 某些联想 Thinkpad 电脑进入 BIOS 后
    1. 需要选择 Security 菜单
    2. 然后选择 Virtualization,设置为 Enabled
  • 某些 DELL(戴尔)电脑进入 BIOS 后
    1. 需要选择 Processor Settings 菜单
    2. 然后选择 Virtualization Technology,设置为 Enabled

解决方案 2

修复 VMWare Workstation

具体设置步骤:

  1. 打开“控制面板”,进入“卸载程序”

  2. 双击 VMWare,不要选择“卸载”,选择“修复”

  3. 完成后,再打开虚拟机即恢复正常

解决方案 3

修改配置文件 macOS.vmx

具体设置步骤:

  1. 在设置的虚拟机挂载的磁盘目录找到 macOS.vmx 文件,用 NotePad 记事本打开这个文件
  2. 确保文件默认添加了这两行代码
smc.version = 0
cpuid.1.eax = "00000000000000010000011010100101"

解决方案 4

禁用 Hyper-V, 然后重启电脑即可

具体设置步骤:

PowerShell 或者 CMD.exe 管理员模式运行:

 # 关闭 Hyper-v,支持VMWare Workstation运行
bcdedit /set hypervisorlaunchtype off

提醒,小编之前在 Windows 还下载安装过 Docker Windows Desktop 软件,用来在 Windows 电脑本地运行 Docker 服务做测试用,发现 Docker 需要开启 Hyper-v 才能用,所以这里是有冲突的,暂时的方案就是用 VMware 虚拟机的时候关闭 Hyper-v,用 Docker 的时候再打开 Hyper-v。用以下命令来重新开启 Hyper-v。

PowerShell 或者 CMD.exe 管理员模式运行:

# 开启 Hyper-v,支持Docker运行,VMWare Workstation无法运行
bcdedit /set hypervisorlaunchtype auto

原文:https://lwebapp.com/zh/post/vmware-vcpu-error

总结

如果以上方法都不能解决,推荐咨询官方专家。如果您发现了其他更有效直接的解决方案,也欢迎告诉小编。

参考

webrtc 屏幕录制

需求

我们在复现软件问题,或者做教学的时候,需要录屏,一般来说需要下载屏幕录制软件,经常还会带上水印,有没有一种可以不用安装软件在线录屏的方案呢,答案就是webrtc,使用浏览器自带的功能,打开网页就能录制桌面上的任意窗口,而且没有水印。

演示

https://dushusir.com/tool/web-recorder.html

代码

以下代码你将学习到
– 使用navigator.mediaDevices.getDisplayMedia获取用户窗口
– 使用MediaRecorder获取用户窗口录制的视频流
– 使用Blob和window.URL.createObjectURL来将视频流下载下来

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>webrtc screen recorder</title>
</head>

<body>


    <video autoplay playsinline id="player"></video>
    <video playsinline id="recordPlayer"></video>

    <!-- 在开始录制之前播放和下载按钮时禁用的 -->
    <button id="recordBtn">start</button>
    <button id="playBtn">play</button>
    <button id="downloadBtn">download</button>

    <script type="text/javascript">


        var player = document.querySelector("#player");
        var recordPlayer = document.querySelector("#recordPlayer");
        var recordBtn = document.querySelector("#recordBtn");
        var playBtn = document.querySelector("#playBtn");
        var downloadBtn = document.querySelector("#downloadBtn");

        var buffer; // 用于存储录制数据(数组)
        var mediaStream;
        var mediaRecoder;

        // 录制按钮点击事件
        recordBtn.addEventListener('click', function () {
            // console.log(recordBtn.text());
            if (recordBtn.textContent === 'start') {
                startRecord();
                recordBtn.textContent = 'stop';
                playBtn.setAttribute('disabled', true);
                downloadBtn.setAttribute('disabled', true);
            } else if (recordBtn.textContent === 'stop') {
                stopRecord();
                recordBtn.textContent = 'start';
                playBtn.removeAttribute('disabled');
                downloadBtn.removeAttribute('disabled');
            }
        });

        // 播放按钮点击事件
        playBtn.addEventListener('click', function () {
            var blob = new Blob(buffer, { type: 'video/webm' });
            // 根据缓存数据生成url给recordPlayer进行播放
            recordPlayer.src = window.URL.createObjectURL(blob);
            recordPlayer.srcObject = null;
            recordPlayer.controls = true; // 显示播放控件
        });

        // 下载按钮点击事件
        downloadBtn.addEventListener('click', function () {
            var blob = new Blob(buffer, { type: 'video/webm' });
            // 根据缓存数据生成url
            var url = window.URL.createObjectURL(blob);
            // 创建一个a标签,通过a标签指向url来下载
            var a = document.createElement('a');
            a.href = url;
            a.style.display = 'none'; // 不显示a标签
            a.download = 'test.webm'; // 下载的文件名
            a.click(); // 调用a标签的点击事件进行下载
        });

        // 开始录制
        function start() {

            var options = { mimeType: 'video/webm;codecs=vp8' };
            if (!MediaRecorder.isTypeSupported(options.mimeType)) {
                console.log('不支持' + options.mimeType);
                return;
            }

            try {
                buffer = [];
                mediaRecoder = new MediaRecorder(mediaStream, options);
            } catch (e) {
                console.log('创建MediaRecorder失败!');
                return;
            }
            mediaRecoder.ondataavailable = handleDataAvailable;
            // 开始录制,设置录制时间片为10ms(每10s触发一次ondataavilable事件)
            mediaRecoder.start(1000);
        }

        // 停止录制
        function stopRecord() {
            mediaRecoder.stop();
            mediaStream.getTracks().forEach(track => track.stop())
        }

        // 触发ondataavilable事件的回调函数
        function handleDataAvailable(e) {
            if (e && e.data && e.data.size > 0) {
                buffer.push(e.data);
            }
        }

        function startRecord() {
            if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
                console.log('不支采集音视频数据!');
            } else {
                // 采集音频数据
                var constrants = {
                    video: true,
                    audio: true
                };
                navigator.mediaDevices.getDisplayMedia(constrants).then(gotMediaStream).catch(handleError);

            }
        }

        // 采集音频数据成功时调用的方法
        function gotMediaStream(stream) {
            mediaStream = stream;
            player.srcObject = stream;

            start()
        }

        // 采集音频数据失败时调用的方法
        function handleError(err) {
            console.log(err.name + ':' + err.message);
        }
    </script>
</body>

</html>

上海疫苗第三针加强针补贴最新信息|持续更新0213

背景

自年前1.26日以来至今日,上海地区已经连续无新增本地新冠病例了,可以说控制的非常棒。

这需要我们继续坚持防疫政策,听从指挥,总有一天疫情会结束的。

现在正在积极开放新冠疫苗第三剂加强针的接种,可以看到很多地铁口都有志愿者或者医务人员举着牌子告诉大家可以接种新冠疫苗了。身边的很多朋友也积极配合去接种了。

小编从业内人士了解到,新冠疫苗第二针接种超过6个月,就可以继续接种第三针加强针了,部分地区还开放了补助名额。小编给大家争取到了一些补贴福利,可以看看以下的补贴要求,看看自己是否可以接种了,一旦时间到了就可以接种了,暂时没有到时间的,也可以先预约着,到了时间直接过去。

最新补贴消息

上海加强针现场补贴🧧130元

要求

  1. 全国打的二针都可以(本社区也要),风险地区不要
  2. 18周岁~60周岁,距离北京生物和北京科兴第二针和康希诺,必须接种半年以上,也就是2021年8月12号之前接种第二针疫苗的
  3. 不要深圳康泰,智飞
  4. 健康码是绿码

注意事项

到现场必须打电话,带身份证原件智能手机,凭小票结账

集合时间

9点30开始

地址

杨浦区

详细了解更多信息 请添加联系人微信

关注文末公众号(技术分社),回复:疫苗,即可获取联系人信息

总结

月底名额开放更多,欢迎添加联系人信息免费咨询。到了时间还没接种第三针疫苗的,要抓紧时间了。

提醒事项

去打第三针加强针疫苗的同事已经领到补贴了。要真正领到补贴,要注意小编接下来要提醒的注意事项。

  1. 关于新冠疫苗接种第三针加强针相关补贴消息,有新消息都会及时更新,因为现在补贴指标很快就满,名额有限,这些地区指标满了之后,就没有补贴了,暂时没有名额的也不用着急,关注后续的补贴信息

  2. 之前发布的补贴地址可能会经常更新。去之前请一定联系好补贴负责人,联系下确认那个地方还有补贴再去,免得白跑一趟。

报名

关注公众号: 技术分社

微信openHacking

  • 回复:疫苗或者补贴. 即可获取疫苗补贴负责人的微信

如果您已经拿到了补贴,也欢迎回来给小编加个鸡腿🍗,让他给大家整理更多补贴优惠信息。

实现人手一个冰墩墩python画冰墩墩源码

背景

最近北京冬奥会正在举行,北京冬奥会的吉祥物“冰墩墩”非常抢手,很多朋友连夜排队都抢不到一个冰墩墩。

冰墩墩实在太火了,为了实现冰墩墩自由、“人手一墩”的梦想,强大的程序员GG奉上了一个用Python绘制冰墩墩的程序,先看下面这个动图体会下

真是太强大了,网友真是无所不能

思路和代码

技术上主要用到了python的turtle和tkinter模块,这两个模块都是Python内置的模块,非常强大。

  • turtle用来绘图,有多种绘制方式和样式
  • tkinter用来构建GUI程序,也内置了许多组件

主程序基本上就是采用turtle,设置不同的画笔和路径等绘制方式,一笔一笔接着在初始化好的tkinter Canvas画布上画出来的。核心功能也就完成了。

先启动一个tkinter界面,放置一个Canvas画布,再将turtle绘制区域设置到这个Canvas上

root = Tk()
root.geometry('600x700+500+60')
root.config(bg='white')
root.title('lwebapp.com')
root.resizable(False, False)
canvas = Canvas(root, width=600, height=600)
canvas.place(x=0,y=50)

t = RawTurtle(canvas)
t.hideturtle()

完整的代码到源码的main.py(https://github.com/openHacking/TKinter-UI/blob/main/demo/draw_bdd/main.py)文件查看

接着就是核心的绘制代码,由于代码太长这里贴出一部分

def draw_bdd(t):

    # reset
    t.penup()
    t.home()
    t.clear()

    # adjust the speed
    t.speed(30)

    # left hand
    t.goto(177, 112)

    t.pencolor("lightgray")

    t.pensize(3)

    t.fillcolor("white")

    t.begin_fill()

    t.pendown()

    t.setheading(80)

    t.circle(-45, 200)

    t.circle(-300, 23)

    t.end_fill()

完整的代码到源码的draw.py(https://github.com/openHacking/TKinter-UI/blob/main/demo/draw_bdd/draw.py)文件查看

最后,用 pytxui 这个python GUI组件库放了两个样式更美观的按钮做重新绘制和下载功能。

下载功能用到了Pillow的ImageGrab截图功能,可以将Canvas上的冰墩墩绘制结果保存为一张PNG图片。

核心的截图保存代码

from PIL import ImageGrab
import os

def save_image(root,widget):
    root.update()
    x=root.winfo_rootx()+widget.winfo_x()
    y=root.winfo_rooty()+widget.winfo_y()

    x1=x+widget.winfo_width()
    y1=y+widget.winfo_height()
    offset = 4
    ImageGrab.grab().crop((x + offset,y + offset,x1 - offset,y1 - offset)).save(os.getcwd() + "\\bdd.png", "PNG")

源码

pytxui-draw_bdd (https://github.com/openHacking/TKinter-UI/tree/main/demo/draw_bdd)

总结

以上我们结合当前大火的冰墩墩学习到了Python相关的知识

  • turtle和tkinter结合使用构造GUI
  • 如何构造一个漂亮的GUI按钮组件
  • 如何将tkinter Canvas画布下载为图片

其中还有很多不足,欢迎给作者多提建议和想法,一起学习提高。

参考

  • pytxui (https://github.com/openHacking/TKinter-UI)