WPF自定义滚动条(含windows和apple两种风格)

测试结构

    <Grid>
        <ListView  VerticalAlignment="Top" HorizontalAlignment="Stretch">
            <ListViewItem>
                <TextBlock Text="xxxx1"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx2"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx3"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx4"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx5"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx6"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx7"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx8"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx9"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx10"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx11"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx12"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx13"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx14"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx15"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx16"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx17"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx18"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx19"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx20"/>
            </ListViewItem>
        </ListView>
    </Grid>

Windows 风格

file

<Style x:Key="ScrollBarLineButton"
       TargetType="{x:Type RepeatButton}">
  <Setter Property="SnapsToDevicePixels"
          Value="True" />
  <Setter Property="OverridesDefaultStyle"
          Value="true" />
  <Setter Property="Focusable"
          Value="false" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type RepeatButton}">
        <Border x:Name="Border"
                Margin="1"
                CornerRadius="2"
                BorderThickness="1">
          <Border.BorderBrush>
            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="0,1">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource BorderMediumColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource BorderDarkColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>
          </Border.BorderBrush>
          <Border.Background>
            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="0,1">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource ControlLightColor}"/>
                  <GradientStop Color="{DynamicResource ControlMediumColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>
          </Border.Background>
          <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
              <VisualState x:Name="Normal" />
              <VisualState x:Name="MouseOver" />
              <VisualState x:Name="Pressed">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                    (GradientBrush.GradientStops)[1].(GradientStop.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlPressedColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>
              <VisualState x:Name="Disabled">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="Arrow"
                                                Storyboard.TargetProperty="(Shape.Fill).
                    (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource DisabledForegroundColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>
            </VisualStateGroup>
          </VisualStateManager.VisualStateGroups>
          <Path x:Name="Arrow"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Data="{Binding Content, 
            RelativeSource={RelativeSource TemplatedParent}}" >
              <Path.Fill>
                  <SolidColorBrush Color="{DynamicResource GlyphColor}"/>
              </Path.Fill>
          </Path>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

<Style x:Key="ScrollBarPageButton"
       TargetType="{x:Type RepeatButton}">
  <Setter Property="SnapsToDevicePixels"
          Value="True" />
  <Setter Property="OverridesDefaultStyle"
          Value="true" />
  <Setter Property="IsTabStop"
          Value="false" />
  <Setter Property="Focusable"
          Value="false" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type RepeatButton}">
        <Border Background="Transparent" />
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

<Style x:Key="ScrollBarThumb"
       TargetType="{x:Type Thumb}">
  <Setter Property="SnapsToDevicePixels"
          Value="True" />
  <Setter Property="OverridesDefaultStyle"
          Value="true" />
  <Setter Property="IsTabStop"
          Value="false" />
  <Setter Property="Focusable"
          Value="false" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Thumb}">
        <Border CornerRadius="2"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="1" />
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

<ControlTemplate x:Key="VerticalScrollBar"
                 TargetType="{x:Type ScrollBar}">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition MaxHeight="18" />
      <RowDefinition Height="0.00001*" />
      <RowDefinition MaxHeight="18" />
    </Grid.RowDefinitions>
    <Border Grid.RowSpan="3"
            CornerRadius="2"
            Background="#F0F0F0" />
    <RepeatButton Grid.Row="0"
                  Style="{StaticResource ScrollBarLineButton}"
                  Height="18"
                  Command="ScrollBar.LineUpCommand"
                  Content="M 0 4 L 8 4 L 4 0 Z" />
    <Track x:Name="PART_Track"
           Grid.Row="1"
           IsDirectionReversed="true">
      <Track.DecreaseRepeatButton>
        <RepeatButton Style="{StaticResource ScrollBarPageButton}"
                      Command="ScrollBar.PageUpCommand" />
      </Track.DecreaseRepeatButton>
      <Track.Thumb>
        <Thumb Style="{StaticResource ScrollBarThumb}"
               Margin="1,0,1,0">
          <Thumb.BorderBrush>

            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="1,0">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource BorderLightColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource BorderDarkColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Thumb.BorderBrush>
          <Thumb.Background>

            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="1,0">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource ControlLightColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource ControlMediumColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Thumb.Background>
        </Thumb>
      </Track.Thumb>
      <Track.IncreaseRepeatButton>
        <RepeatButton Style="{StaticResource ScrollBarPageButton}"
                      Command="ScrollBar.PageDownCommand" />
      </Track.IncreaseRepeatButton>
    </Track>
    <RepeatButton Grid.Row="2"
                  Style="{StaticResource ScrollBarLineButton}"
                  Height="18"
                  Command="ScrollBar.LineDownCommand"
                  Content="M 0 0 L 4 4 L 8 0 Z" />
  </Grid>
</ControlTemplate>

<ControlTemplate x:Key="HorizontalScrollBar"
                 TargetType="{x:Type ScrollBar}">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition MaxWidth="18" />
      <ColumnDefinition Width="0.00001*" />
      <ColumnDefinition MaxWidth="18" />
    </Grid.ColumnDefinitions>
    <Border Grid.ColumnSpan="3"
            CornerRadius="2"
            Background="#F0F0F0" />
    <RepeatButton Grid.Column="0"
                  Style="{StaticResource ScrollBarLineButton}"
                  Width="18"
                  Command="ScrollBar.LineLeftCommand"
                  Content="M 4 0 L 4 8 L 0 4 Z" />
    <Track x:Name="PART_Track"
           Grid.Column="1"
           IsDirectionReversed="False">
      <Track.DecreaseRepeatButton>
        <RepeatButton Style="{StaticResource ScrollBarPageButton}"
                      Command="ScrollBar.PageLeftCommand" />
      </Track.DecreaseRepeatButton>
      <Track.Thumb>
        <Thumb Style="{StaticResource ScrollBarThumb}"
               Margin="0,1,0,1">

          <Thumb.BorderBrush>

            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="1,0">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource BorderLightColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource BorderDarkColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Thumb.BorderBrush>
          <Thumb.Background>

            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="0,1">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource ControlLightColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource ControlMediumColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Thumb.Background>
        </Thumb>
      </Track.Thumb>
      <Track.IncreaseRepeatButton>
        <RepeatButton Style="{StaticResource ScrollBarPageButton}"
                      Command="ScrollBar.PageRightCommand" />
      </Track.IncreaseRepeatButton>
    </Track>
    <RepeatButton Grid.Column="2"
                  Style="{StaticResource ScrollBarLineButton}"
                  Width="18"
                  Command="ScrollBar.LineRightCommand"
                  Content="M 0 0 L 4 4 L 0 8 Z" />
  </Grid>
</ControlTemplate>

<Style x:Key="{x:Type ScrollBar}"
       TargetType="{x:Type ScrollBar}">
  <Setter Property="SnapsToDevicePixels"
          Value="True" />
  <Setter Property="OverridesDefaultStyle"
          Value="true" />
  <Style.Triggers>
    <Trigger Property="Orientation"
             Value="Horizontal">
      <Setter Property="Width"
              Value="Auto" />
      <Setter Property="Height"
              Value="18" />
      <Setter Property="Template"
              Value="{StaticResource HorizontalScrollBar}" />
    </Trigger>
    <Trigger Property="Orientation"
             Value="Vertical">
      <Setter Property="Width"
              Value="18" />
      <Setter Property="Height"
              Value="Auto" />
      <Setter Property="Template"
              Value="{StaticResource VerticalScrollBar}" />
    </Trigger>
  </Style.Triggers>
</Style>

需要的资源

<!--Control colors.-->
<Color x:Key="WindowColor">#FFE8EDF9</Color>
<Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color>
<Color x:Key="ContentAreaColorDark">#FF7381F9</Color>

<Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
<Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
<Color x:Key="DisabledForegroundColor">#FF888888</Color>

<Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
<Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>

<Color x:Key="ControlLightColor">White</Color>
<Color x:Key="ControlMediumColor">#FF7381F9</Color>
<Color x:Key="ControlDarkColor">#FF211AA9</Color>

<Color x:Key="ControlMouseOverColor">#FF3843C4</Color>
<Color x:Key="ControlPressedColor">#FF211AA9</Color>

<Color x:Key="GlyphColor">#FF444444</Color>
<Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color>

<!--Border colors-->
<Color x:Key="BorderLightColor">#FFCCCCCC</Color>
<Color x:Key="BorderMediumColor">#FF888888</Color>
<Color x:Key="BorderDarkColor">#FF444444</Color>

<Color x:Key="PressedBorderLightColor">#FF888888</Color>
<Color x:Key="PressedBorderDarkColor">#FF444444</Color>

<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
<Color x:Key="DisabledBorderDarkColor">#FF888888</Color>

<Color x:Key="DefaultBorderBrushDarkColor">Black</Color>

<!--Control-specific resources.-->
<Color x:Key="HeaderTopColor">#FFC5CBF9</Color>
<Color x:Key="DatagridCurrentCellBorderColor">Black</Color>
<Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color>

<Color x:Key="NavButtonFrameColor">#FF3843C4</Color>

<LinearGradientBrush x:Key="MenuPopupBrush"
                     EndPoint="0.5,1"
                     StartPoint="0.5,0">
  <GradientStop Color="{DynamicResource ControlLightColor}"
                Offset="0" />
  <GradientStop Color="{DynamicResource ControlMediumColor}"
                Offset="0.5" />
  <GradientStop Color="{DynamicResource ControlLightColor}"
                Offset="1" />
</LinearGradientBrush>

<LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill"
                     StartPoint="0,0"
                     EndPoint="1,0">
  <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
      <GradientStop Color="#000000FF"
                    Offset="0" />
      <GradientStop Color="#600000FF"
                    Offset="0.4" />
      <GradientStop Color="#600000FF"
                    Offset="0.6" />
      <GradientStop Color="#000000FF"
                    Offset="1" />
    </GradientStopCollection>
  </LinearGradientBrush.GradientStops>
</LinearGradientBrush>

Apple风格

file

<!--Scrollbar Thumbs-->
<Style x:Key="ScrollThumbs" TargetType="{x:Type Thumb}">
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type Thumb}">
            <Grid x:Name="Grid">
                <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Fill="Transparent" />
                <Border x:Name="Rectangle1" CornerRadius="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Background="{TemplateBinding Background}" />
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="Tag" Value="Horizontal">
                    <Setter TargetName="Rectangle1" Property="Width" Value="Auto" />
                    <Setter TargetName="Rectangle1" Property="Height" Value="7" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Setter.Value>
</Setter>
</Style>
<!--ScrollBars-->
<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
<Setter Property="Stylus.IsFlicksEnabled" Value="false" />
<Setter Property="Foreground" Value="#8C8C8C" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Width" Value="8" />
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type ScrollBar}">
            <Grid x:Name="GridRoot" Width="8" Background="{TemplateBinding Background}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="0.00001*" />
                </Grid.RowDefinitions>
                <Track x:Name="PART_Track" Grid.Row="0" IsDirectionReversed="true" Focusable="false">
                    <Track.Thumb>
                        <Thumb x:Name="Thumb" Background="{TemplateBinding Foreground}" Style="{DynamicResource ScrollThumbs}" />
                    </Track.Thumb>
                    <Track.IncreaseRepeatButton>
                        <RepeatButton x:Name="PageUp" Command="ScrollBar.PageDownCommand" Opacity="0" Focusable="false" />
                    </Track.IncreaseRepeatButton>
                    <Track.DecreaseRepeatButton>
                        <RepeatButton x:Name="PageDown" Command="ScrollBar.PageUpCommand" Opacity="0" Focusable="false" />
                    </Track.DecreaseRepeatButton>
                </Track>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger SourceName="Thumb" Property="IsMouseOver" Value="true">
                    <Setter Value="{DynamicResource ButtonSelectBrush}" TargetName="Thumb" Property="Background" />
                </Trigger>
                <Trigger SourceName="Thumb" Property="IsDragging" Value="true">
                    <Setter Value="{DynamicResource DarkBrush}" TargetName="Thumb" Property="Background" />
                </Trigger>
                <Trigger Property="IsEnabled" Value="false">
                    <Setter TargetName="Thumb" Property="Visibility" Value="Collapsed" />
                </Trigger>
                <Trigger Property="Orientation" Value="Horizontal">
                    <Setter TargetName="GridRoot" Property="LayoutTransform">
                        <Setter.Value>
                            <RotateTransform Angle="-90" />
                        </Setter.Value>
                    </Setter>
                    <Setter TargetName="PART_Track" Property="LayoutTransform">
                        <Setter.Value>
                            <RotateTransform Angle="-90" />
                        </Setter.Value>
                    </Setter>
                    <Setter Property="Width" Value="Auto" />
                    <Setter Property="Height" Value="8" />
                    <Setter TargetName="Thumb" Property="Tag" Value="Horizontal" />
                    <Setter TargetName="PageDown" Property="Command" Value="ScrollBar.PageLeftCommand" />
                    <Setter TargetName="PageUp" Property="Command" Value="ScrollBar.PageRightCommand" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Setter.Value>
</Setter>
</Style>

参考

Livewire 快速上手教程

Livewire – 不用一行JS实现vue/ng/react等框架的数据绑定效果

理解Livewire

构建现代的Web应用程序很困难。 Vue和React之类的工具功能非常强大,但是它们增加了全栈开发人员工作流程的复杂性。

Livewire是Laravel的全栈框架,可简化构建交互式页面的过程。不用一行JS实现vue/ng/react等框架的数据绑定效果。

理解它的最好方法就是看代码。

下面构建一个搜索组件,当用户键入搜索输入时,用户列表将实时更新。

组件PHP代码:

use Livewire\Component;

class SearchUsers extends Component
{
    public $search = '';

    public function render()
    {
        return view('livewire.search-users', [
            'users' => User::where('username', $this->search)->get(),
        ]);
    }
}

组件视图

<div>
    <input wire:model="search" type="text" placeholder="Search users..."/>

    <ul>
        @foreach($users as $user)
            <li>{{ $user->username }}</li>
        @endforeach
    </ul>
</div>

引用组件

<body>
    ...
    @livewire('search-users')
    ...
</body>

快速上手教程

教程非常简单,客户端点击一次按钮,实现客户端和服务端计数器同时加一。

安装 Livewire

composer require livewire/livewire

在视图中保护livewire的css和js

    @livewireStyles
</head>
<body>
    ...

    @livewireScripts
</body>
</html>

新增组件

php artisan make:livewire counter

组件文件内容

class Counter extends Component
{
    public $count = 0;

    public function increment()
    {
        $this->count++;
    }

    public function render()
    {
        return view('livewire.counter');
    }
}

组件视图

<div style="text-align: center">
    <button wire:click="increment">+</button>
    <h1>{{ $count }}</h1>
</div>

在视图中引用组件

<head>
    ...
    @livewireStyles
</head>
<body>
    <livewire:counter />

    ...

    @livewireScripts
</body>
</html>

相关资源

通过Xvfb在命令行界面运行GUI程序

wkhtmltopdf使用的qt版本比较久,不支持很多css3和html5特性,于是用qt写了一个html转PDF工具,需要常驻后台运行,采用supervisord守护,无需安装图形界面,这时候需要用到Xvfb。

Xvfb介绍

Xvfb or X virtual framebuffer is a display server implementing the X11 display server protocol. In contrast to other display servers, Xvfb performs all graphical operations in virtual memory without showing any screen output.

简单说就是Xvfb是一个虚拟的X11显示服务,在虚拟内存中执行不需要显示图像。可以用来做远程左面显示,以及将桌面程序在非桌面环境使用(如headless的图形界面测试)。

Xvfb基本使用

安装xvfb

sudo apt-get install xvfb

命令行运行虚拟桌面

Xvfb :2 -screen 0 1024x768x16 &
x11vnc -listen 0.0.0.0 -rfbport 5900 -noipv6 -passwd xxxxxx -display 2
export DISPLAY=:2

使用Python xvfbwrapper

from xvfbwrapper import Xvfb

with Xvfb(width=1920, height=1024, colordepth=24) as xvfb:
    # launch stuff inside virtual display here.
    # Xvfb will stop when this block completes

参考

WSL不支持systemctl与service命令解决办法(ubuntu/arch/manjaro等)

ubuntu-wsl2-systemd-script(适用ubuntu)

sudo apt install git
git clone https://github.com/DamionGans/ubuntu-wsl2-systemd-script.git
cd ubuntu-wsl2-systemd-script/
bash ubuntu-wsl2-systemd-script.sh

genie(适用arch/manjaro/centos等)

yay dotnet
yay -S genie-systemd

ubuntu使用国内源

sudo sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
sudo apt update -y

启用适用于 Linux 的 Windows 子系统

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

更新到 WSL 2

要求

  • 对于 x64 系统: 版本 1903 或更高版本,采用 内部版本 18362 或更高版本。
  • 对于 ARM64 系统: 版本 2004 或更高版本,采用 内部版本 19041 或更高版本。
  • 低于 18362 的版本不支持 WSL 2。 使用 Windows Update 助手更新 Windows 版本。

若要检查 Windows 版本及内部版本号,选择 Windows 徽标键 + R,然后键入“winver”,选择“确定”。 (或者在 Windows 命令提示符下输入 ver 命令)。 更新到“设置”菜单中的最新 Windows 版本。

启用虚拟机功能

dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

下载 Linux 内核更新包

适用于 x64 计算机的 WSL2 Linux 内核更新包

将 WSL 2 设置为默认版本

wsl --set-default-version 2

安装所选的 Linux 分发

打开 Microsoft Store,并选择你偏好的 Linux 分发版。

安装 Windows 终端 (可选)

https://docs.microsoft.com/zh-cn/windows/terminal/get-started

参考

解决docker容器中apt-get、pip慢问题

以debian/ubuntu为例,修改Dockerfile使用deiban和pip镜像。

RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN sed -i 's|security.debian.org/debian-security|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple \
    && pip config set install.trusted-host mirrors.aliyun.com

使用npm镜像

RUN npm config set registry https://registry.npm.taobao.org

参考

php redis连接数过多解决办法(Yii,predis,phpredis等)

Yii redis 持久配置

   'redis' => [
            'class' => 'yii\redis\Connection',
            'hostname' => '127.0.0.1',
            'retries' => 3,
            'port' => 6379,
            'password' => '',
            'socketClientFlags' => STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
        ],

predis redis 持久配置

  $client = new \Predis\Client([
            'scheme' => 'tcp',
            'host' => $redis->hostname,
            'port' => $redis->port,
            'password' => $redis->password,
            'persistent'=>true,
        ]);

phpredis redis 持久配置

$redis->pconnect('127.0.0.1', 6379);

nginx/python/mysql/fpm/go连接数解决办法

Django工作流(状态管理)

本文通过简单的例子比较了django状态管理扩展的使用,涵盖django-fsm,xworkflows,django_transitions以及参考

django-fsm 使用例子

https://github.com/viewflow/django-fsm

state = FSMField(
    default=State.DRAFT,
    verbose_name='Publication State',
    choices=State.CHOICES,
    protected=True,
)
@transition(field=state, source=[State.APPROVED, State.EXPIRED],
    target=State.PUBLISHED,
    conditions=[can_display])
def publish(self):
    '''
    Publish the object.
    '''
    email_the_team()
    update_sitemap()
    busta_cache()

django-fsm-admin

https://github.com/gadventures/django-fsm-admin

from fsm_admin.mixins import FSMTransitionMixin

class YourModelAdmin(FSMTransitionMixin, admin.ModelAdmin):
    # The name of one or more FSMFields on the model to transition
    fsm_field = ['state',]
    readonly_fields = ['state', ]

admin.site.register(YourModel, YourModelAdmin)

xworkflows

https://github.com/rbarrois/xworkflows

import xworkflows

class MyWorkflow(xworkflows.Workflow):
    # A list of state names
    states = (
        ('foo', "Foo"),
        ('bar', "Bar"),
        ('baz', "Baz"),
    )
    # A list of transition definitions; items are (name, source states, target).
    transitions = (
        ('foobar', 'foo', 'bar'),
        ('gobaz', ('foo', 'bar'), 'baz'),
        ('bazbar', 'baz', 'bar'),
    )
    initial_state = 'foo'

class MyObject(xworkflows.WorkflowEnabled):
    state = MyWorkflow()

    @xworkflows.transition()
    def foobar(self):
        return 42

    # It is possible to use another method for a given transition.
    @xworkflows.transition('gobaz')
    def blah(self):
        return 13

django_transitions

LiveStatus Status

class LiveStatus(StatusBase):
    """Workflow for Lifecycle."""

    # Define the states as constants
    DEVELOP = 'develop'
    LIVE = 'live'
    MAINTENANCE = 'maintenance'
    DELETED = 'deleted'

    # Give the states a human readable label
    STATE_CHOICES = (
        (DEVELOP, 'Under Development'),
        (LIVE, 'Live'),
        (MAINTENANCE, 'Under Maintenance'),
        (DELETED, 'Deleted'),
    )

    # Define the transitions as constants
    PUBLISH = 'publish'
    MAKE_PRIVATE = 'make_private'
    MARK_DELETED = 'mark_deleted'
    REVERT_DELETED = 'revert_delete'

    # Give the transitions a human readable label and css class
    # which will be used in the django admin
    TRANSITION_LABELS = {
        PUBLISH : {'label': 'Make live', 'cssclass': 'default'},
        MAKE_PRIVATE: {'label': 'Under maintenance'},
        MARK_DELETED: {'label': 'Mark as deleted', 'cssclass': 'deletelink'},
        REVERT_DELETED: {'label': 'Revert Delete', 'cssclass': 'default'},
    }

    # Construct the values to pass to the state machine constructor

    # The states of the machine
    SM_STATES = [
        DEVELOP, LIVE, MAINTENANCE, DELETED,
    ]

    # The machines initial state
    SM_INITIAL_STATE = DEVELOP

    # The transititions as a list of dictionaries
    SM_TRANSITIONS = [
        # trigger, source, destination
        {
            'trigger': PUBLISH,
            'source': [DEVELOP, MAINTENANCE],
            'dest': LIVE,
        },
        {
            'trigger': MAKE_PRIVATE,
            'source': LIVE,
            'dest': MAINTENANCE,
        },
        {
            'trigger': MARK_DELETED,
            'source': [
                DEVELOP, LIVE, MAINTENANCE,
            ],
            'dest': DELETED,
        },
        {
            'trigger': REVERT_DELETED,
            'source':  DELETED,
            'dest': MAINTENANCE,
        },
    ]

Model:

class Lifecycle(LifecycleStateMachineMixin, models.Model):
    """
    A model that provides workflow state and workflow date fields.

    This is a minimal example implementation.
    """

    class Meta:  # noqa: D106
        abstract = False

    wf_state = models.CharField(
        verbose_name = 'Workflow Status',
        null=False,
        blank=False,
        default=LiveStatus.SM_INITIAL_STATE,
        choices=LiveStatus.STATE_CHOICES,
        max_length=32,
        help_text='Workflow state',
    )

    wf_date =  models.DateTimeField(
        verbose_name = 'Workflow Date',
        null=False,
        blank=False,
        default=timezone.now,
        help_text='Indicates when this workflowstate was entered.',
    )

Admin

# -*- coding: utf-8 -*-
"""Example django admin."""

from django_transitions.admin import WorkflowAdminMixin
from django.contrib import admin

from .models import Lifecycle

class LifecycleAdmin(WorkflowAdminMixin, admin.ModelAdmin):
    """
    Minimal Admin for Lifecycles Example.

    You probably want to make the workflow fields
    read only so yo can not change these values
    manually.

    readonly_fields = ['wf_state', 'wf_date']
    """

    list_display = ['wf_date', 'wf_state']
    list_filter = ['wf_state']

admin.site.register(Lifecycle, LifecycleAdmin)

参考

使用MSYS2+mingw+QT作为C++开发环境

 下载msys2

在中国科学技术大学镜像站点http://mirrors.ustc.edu.cn/msys2/distrib/下载最新版32(i686)/64(x86_64)位系统msys2

下载后安装

修改软件源

msys2采用pacman管理软件包,修改pacman的软件源提高下载速度

pacman的配置
编辑 /etc/pacman.d/mirrorlist.mingw32 ,在文件开头添加:

Server = http://mirrors.ustc.edu.cn/msys2/mingw/i686/
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686/
Server = http://mirror.bit.edu.cn/msys2/mingw/i686/
Server = https://mirrors.sjtug.sjtu.edu.cn/msys2/mingw/i686/

编辑 /etc/pacman.d/mirrorlist.mingw64 ,在文件开头添加

Server = http://mirrors.ustc.edu.cn/msys2/mingw/x86_64/
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/x86_64/
Server = http://mirror.bit.edu.cn/msys2/mingw/x86_64/
Server = https://mirrors.sjtug.sjtu.edu.cn/msys2/mingw/x86_64/

编辑 /etc/pacman.d/mirrorlist.msys ,在文件开头添加

Server = http://mirrors.ustc.edu.cn/msys2/msys/$arch/
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/msys/$arch/
Server = http://mirror.bit.edu.cn/msys2/msys/$arch/
Server = https://mirrors.sjtug.sjtu.edu.cn/msys2/msys/$arch/

然后执行 pacman -Sy 刷新软件包数据即可。

安装开发环境

安装基本开发环境和常用工具

pacman -S  base-devel  git  wget  p7zip  perl ruby python

安装 32位 MinGW-w64

pacman -S  mingw-w64-i686-toolchain

安装 64位 MinGW-w64

pacman -S  mingw-w64-x86_64-toolchain

安装Qt5和Qt Creator

pacman -S mingw64/mingw-w64-x86_64-qt5 mingw64/mingw-w64-x86_64-qt-creator

参考

GlitchTip – 开源的Sentry API兼容的错误跟踪系统

GlitchTip is an open source, Sentry API compatible error tracking platform. It is a partial fork/partial reimplementation of Sentry’s open source codebase before it went proprietary. Its goals are to be a modern, easy-to-develop error tracking platform that respects your freedom to use it any way you wish. Some differences include:

A modern development environment with Python 3 and Django 3.1.
Simplicity over features. We use Postgres to store error data. Our code base is a fraction of the size of Sentry and looks like a typical Django app. We leverage existing open source Django ecosystem apps whenever possible.
Respects your privacy. No massive JS bundles. No invasive tracking. No third party spying. Even our marketing site runs Matomo and respects Do Not Track. GlitchTip will never report home. We will never know if you run it yourself.
Commitment to open source. We use open source tools like GitLab whenever possible. With our MIT license, you can use it for anything you’d like and even sell it. We believe in competition and hope you make GlitchTip even better.

Project status: Stable. Open an issue and say hello if you’d like to help. We are able to process basic error requests from the open source Sentry client tools. More features are on the way.

参考

PHP AMQP 使用指南

为什么要使用消息队列?

提速

使用了消息队列,生产者一方,把消息往队列里一扔,就可以立马返回,响应用户了。无需等待处理结果。

处理结果可以让用户稍后自己来取,如医院取化验单。也可以让生产者订阅(如:留下手机号码或让生产者实现listener接口、加入监听队列),有结果了通知。获得约定将结果放在某处,无需通知。

解耦

考虑电商系统下订单,发送数据给生产系统的情况。电商系统和生产系统之间的网络有可能掉线,生产系统可能会因维护等原因暂停服务。如果不使用消息队列,电商系统数据发布出去,顾客无法下单,影响业务开展。两个系统间不应该如此紧密耦合。应该通过消息队列解耦。同时让系统更健壮、稳定。

消除峰值

消息存在队列中,等待高峰期过后再处理

广播

多个客户端可以订阅同一个TOPIC,或者多个worker轮询并发执行任务

CentOS 安装 RabbitMQ

安装并启动rabbitmq

yum install rabbitmq-server
systemctl start rabbitmq-server
systemctl status rabbitmq-server
systemctl enable rabbitmq-server
rabbitmq-plugins enable rabbitmq_management

启动管理后台插件

rabbitmq-plugins enable rabbitmq_management

添加管理账户

rabbitmqctl add_user admin password
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

开启端口

amqp端口是 5672 , 管理后台是 15672

RabbitMQ management API

https://rawcdn.githack.com/rabbitmq/rabbitmq-management/v3.8.9/priv/www/api/index.html

可以通过API管理RabbitMQ服务器

PHP的amqp扩展

php-amqplib 纯PHP库

https://github.com/php-amqplib/php-amqplib

composer 安装

composer require php-amqplib/php-amqplib

perl c扩展

https://pecl.php.net/package/amqp

linux 编译安装

pecl install amqp

centos yum remi库安装

 yum install php74-php-pecl-amqp

windows dll 安装

https://pecl.php.net/package/amqp

下载后php_amqp.dll放到ext目录,rabbitmq.4.dll放在php目录,修改php.ini添加extension=php_amqp.dll

使用AMQP

https://www.rabbitmq.com/tutorials/tutorial-one-php.html

参考