Skip to main content

· 4 min read

https://blog.csdn.net/wolanx/article/details/124599294

Intro

在尝试了FC,CNN等模型在 mnist 的练习后,使用 torchvision.models 的官方定义尝试运行 vgg16,resnet。 常见会出现以下错误:

  • RuntimeError: Given groups=1, weight of size [64, 3, 3, 3], expected input[64, 1, 28, 28] to have 3 channels, but got 1 channels instead
  • RuntimeError: Given input size: (512x1x1). Calculated output size: (512x0x0). Output size is too small

模型定义如下

# mnist cnn 不知道怎么写的,可以参考 https://github.com/wolanx/pii/blob/main/x10_ml/demo2-6_mnist/demo2-6.ipynb
dataset1 = torchvision.datasets.MNIST(root="./data", train=True, download=True, transform=transform)

model = torchvision.models.vgg16(pretrained=False, num_classes=10)
model = model.to(device)
print(model)
VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace=True)
(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU(inplace=True)
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
...
)
(avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
(classifier): Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): ReLU(inplace=True)
...
)
)

错误一

Given groups=1, weight of size [64, 3, 3, 3], expected input[64, 1, 28, 28] to have 3 channels, but got 1 channels instead

是说维度不同,mnist里的数据为单色28px28px,所以是(1, 28, 28),但vgg是面向RGB图像是三色的,需要把它变成RGB的3通道,但是显然要自己去搞这样的数据太麻烦。换个思路,查看vgg16的定义把vgg的features的第一层修改下

VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 改这一层
(1): ReLU(inplace=True)

修改完如下

model = torchvision.models.vgg16(pretrained=False, num_classes=10)
model.features[0] = nn.Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 这里的 conv2d(3,64) 改成了 conv2d(1,64)
model = model.to(device)
print(model) # 再次检查下
VGG(
(features): Sequential(
(0): Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 改这一层
(1): ReLU(inplace=True)

到这一步,错误一就解决了,但紧接着会出现错误二

错误二

Given input size: (512x1x1). Calculated output size: (512x0x0). Output size is too small

这一步说的太抽象,先回顾下vgg16的几层模型,需要注意到,图像经过多次conv,而conv stride=1时,相当于多图像进行了缩小(一半),而 mnist 的像素为 28 28,缩小一半为 14 14,再 7 7,再 4 4,再 2 2,再 1 1,再就 0 * 0。而vgg层数还没运行到最后就没了。和错误一相似,一里是RGB不满足,错误二是像素不满足,同样要换样本不现实。简单处理就是,不要不断的缩小,减层可以,但是会破坏定义结构。但也可以是反卷积:

model = torchvision.models.vgg16(pretrained=False, num_classes=10)
model.features[0] = nn.Conv2d(1, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 不是 (3,64) (1,16),单通道再加小一点
model.features[2] = nn.ConvTranspose2d(16, 64, kernel_size=(3, 3), stride=(2, 2), padding=(0, 0), bias=False)
model = model.to(device)
print(model) # 再次检查下
VGG(
(features): Sequential(
(0): Conv2d(1, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 这里变了
(1): ReLU(inplace=True)
(2): ConvTranspose2d(16, 64, kernel_size=(3, 3), stride=(2, 2), bias=False) # 这里变了

这样就解决了像素太低问题,整个模型至少现在能跑了,但是如果有做train的话,发现error实在收敛太慢,还需要再优化下。

· 11 min read

https://blog.csdn.net/wolanx/article/details/123526472

Intro

在使用的相当一段时间的 threejsreact-three-fiber 后,在中文资料环境极其匮乏的情况下,做个极简·笔记式的分享。目标是能让大家在 最快 的速度上手,且 半·精通

Install

npm install three @react-three/fiber 官方文档

Demo1 - 全局概览

![](https://img-blog.csdnimg.cn/1845e0529fbf434388ba99b656eaf466.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAeXVqaWUuemhhbw==,size_13,color_FFFFFF,t_70,g_se,x_16 =250x)

这是一个短小精悍的 demo1,对着下面代码看解析

  • 物体 盒子:new BoxGeometry() 球:new SphereGeometry(.5, 32, 16)
  • 物体上色 <mesh geometry={ball} material={mtl1} /> 使用 mesh 将 物体和材料 捆绑
  • 光1 <ambientLight intensity={0.1} /> 环境光,四面八方的光,intensity 光的强度
  • 光2 <directionalLight /> 一束光 需要 color position:照射方向
  • 控制 OrbitControls 交互操作,鼠标 旋转:左键拖拽 平移:右键拖拽 放大:滚轮
  • 阴影 <ContactShadows /> 参数字面意思
  • 背景 <color attach='background' args={['#aaa']} /> 也可以用 css 解决
import React, { Suspense } from 'react'
import { BoxGeometry, MeshStandardMaterial } from 'three'
import { Canvas } from '@react-three/fiber'
import { ContactShadows, OrbitControls } from '@react-three/drei'

const ball = new BoxGeometry()
const mtl1 = new MeshStandardMaterial({ color: '#f00' })

export default function Demo () {
return (
<Canvas style={{ height: 800 }} camera={{ fov: 75, near: 0.1, far: 1000, position: [2, 1, 2] }}>
<Suspense fallback={null}>
<ambientLight intensity={0.1} />
<directionalLight color={'#fff'} intensity={1} position={[-3, 5, 5]} />
<mesh geometry={ball} material={mtl1} />
<OrbitControls makeDefault />
<ContactShadows rotation-x={Math.PI / 2} position={[0, -1.4, 0]} opacity={0.75} width={10} height={10} blur={2.6} far={2} />
<color attach='background' args={['#aaa']} />
</Suspense>
</Canvas>
)
}

如果你认真看完 demo1 每一处细节,你已经对 threejs 有了 50% 的认识了。。。后续就是对每个部分的展开和丰富

Demo2 - 文件加载

实际项目并不会像 demo1 中使用系统物体,通常是 外部文件,常见格式有 obj,gltf,glb,fbx 等。 demo2 从外部加载,并添加了一些常用 工具

  • 远程加载 useLoader(OBJLoader, src) 不同的格式,只需要切换加载类,快捷版:useFBX(src) useGLTF(src, true)
  • 坐标系 new AxesHelper(100) 方便查看三维世界
  • 性能工具 <Stats showPanel={0} parent={statRef} /> fps,需要通过 ref 来控制位置
  • 环境设置 <Environment /> 具体看代码 6个贴面(东南西北+上下),主要控制光反射
  • 错误抑制 <ErrorBoundary> 出错以后,可以把错误局限在这里面,不影响整体框架,应用了 antd 的,也可以自己实现或者不要
import React, { Suspense, useRef } from 'react'
import { AxesHelper } from 'three'
import { Canvas, useLoader } from '@react-three/fiber'
import { Environment, OrbitControls, Stats } from '@react-three/drei'
import ErrorBoundary from 'antd/es/alert/ErrorBoundary'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'

function MyMesh () {
const src = '/obj/demo2.obj'
const object = useLoader(OBJLoader, src)
console.log(object)
return (
<primitive object={object} />
)
}

export default function Demo () {
const statRef = useRef(null)
return (
<div ref={statRef}>
<Stats showPanel={0} parent={statRef} style={{ top: 'auto', bottom: 0 }} />
<ErrorBoundary>
<Canvas style={{ height: 800 }} camera={{ fov: 75, near: 0.1, far: 1000, position: [2, 1, 2] }}>
<Suspense fallback={null}>
<directionalLight color={'#fff'} intensity={1} position={[-3, 5, 5]} />
<primitive object={new AxesHelper(100)} />
<MyMesh />
<OrbitControls makeDefault />
<color attach='background' args={['#aaa']} />
<Environment
background={false} preset={null} scene={undefined}
path={'/img/three/env/'}
files={['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png']}
/>
</Suspense>
</Canvas>
</ErrorBoundary>
</div>
)
}

如果不出意外,你的模型已经出来了,但是有 一半 的可能你只是一个灰白物体,没上色。因为obj格式的 材质 部分是有要求的。首先 obj 可能没直接包含 mtl 文件,或者 mtl 文件没有相对路径于 obj 文件(同一目录)。其次 mtl 中的图片定义,也没有相对路径于 obj 文件。 情况复杂且多,需要大家根据 Network 自我 debug。

Demo3 自动对焦

这个需求很多,网上解释也很多,但我是没看懂,也没见人真解决。在翻看 threejs-editor 时,发现了 auto-focus 的源码(editor/js/EditorControls.js:34 focus),然后进行改装,这边给个参考。过程:先计算物体边界,转化成盒子模型,然后倍数,算出合适的位置 + 角度,然后设置回去。

import React, { Suspense, useEffect, useState } from 'react'
import { Box3, Sphere, Vector3 } from 'three'
import { Canvas, useLoader } from '@react-three/fiber'
import { OrbitControls, PerspectiveCamera } from '@react-three/drei'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'

const OBJ_POS_000 = [0, 0, 0]
const CAM_POS_212 = [1, .5, 1]

function calcBound (object, x = 1.5) {
const box = new Box3().setFromObject(object)

const center = new Vector3()
let distance
let objPos2 = new Vector3()

if (box.isEmpty() === false) {
/** @var {Vector3} */
objPos2 = box.getCenter(center)
objPos2.negate()
distance = box.getBoundingSphere(new Sphere()).radius
} else {
center.setFromMatrixPosition(object.matrixWorld)
distance = 0.1
}

const delta = new Vector3()
delta.set(...CAM_POS_212)
delta.applyQuaternion(object.quaternion)
delta.multiplyScalar(distance * x)
const camPos2 = new Vector3()
camPos2.copy(objPos2).add(delta)

return [objPos2.toArray(), camPos2.toArray()]
}

function MyMesh () {
const [objPos, setObjPos] = useState(OBJ_POS_000)
const [camPos, setCamPos] = useState(CAM_POS_212)

const src = '/obj/demo2.obj'
const object = useLoader(OBJLoader, src)
useEffect(() => {
const [objPos2, camPos2] = calcBound(object, 2.5)
setObjPos(objPos2)
setCamPos(camPos2)
// eslint-disable-next-line
}, [object])

return (
<>
<primitive object={object} position={objPos} />
<PerspectiveCamera makeDefault fov={50} far={5000} near={.01} position={camPos} />
</>
)
}

export default function Demo () {
return (
<Canvas style={{ height: 800 }} camera={{ fov: 75, near: 0.1, far: 1000, position: [2, 1, 2] }}>
<Suspense fallback={null}>
<ambientLight intensity={0.1} />
<directionalLight color={'#fff'} intensity={1} position={[-3, 5, 5]} />
<MyMesh />
<OrbitControls makeDefault />
<color attach='background' args={['#aaa']} />
</Suspense>
</Canvas>
)
}

进阶部分

前面的 demo 都给了完整可运行代码,后面部分由于需要配套的基础代码太多,只能给 关键部分。大家自行整合,实在不行,留言询问 up。

编辑物体位置、旋转、缩放

这个 demo 来自网上,但是缺少保存的过程,这边给个保存参考

// 保存逻辑参考

const { gl, scene, camera } = useThree()
// 前提是要有 uuid
const obj = scene.getObjectByName(uuid)
const partObj = packRef.current.getObjByUuid(uuid)
const position = partObj.position.toArray()
const rotation = partObj.rotation.toVector3().toArray()
console.log(partObj, position, rotation)
// 然后自己定义数据结构,入库
import { Suspense, useState } from 'react'
import { Canvas, useThree } from '@react-three/fiber'
import { ContactShadows, OrbitControls, TransformControls, useCursor, useGLTF } from '@react-three/drei'
import { proxy, useSnapshot } from 'valtio'

// Reactive state model, using Valtio ...
const modes = ['translate', 'rotate', 'scale']
const state = proxy({ current: null, mode: 0 })

function Model ({ name, ...props }) {
// Ties this component to the state model
const snap = useSnapshot(state)
// Fetching the GLTF, nodes is a collection of all the meshes
// It's cached/memoized, it only gets loaded and parsed once
const { nodes } = useGLTF('https://utihp1.csb.app/compressed.glb')
// Feed hover state into useCursor, which sets document.body.style.cursor to pointer|auto
const [hovered, setHovered] = useState(false)
useCursor(hovered)
return (
<mesh
// Click sets the mesh as the new target
onClick={(e) => {
e.stopPropagation()
state.current = name
}}
// If a click happened but this mesh wasn't hit we null out the target,
// This works because missed pointers fire before the actual hits
onPointerMissed={(e) => {if (e.type === 'click') state.current = null}}
// Right click cycles through the transform modes
onContextMenu={(e) => {
if (snap.current === name) {
e.stopPropagation()
state.mode = (snap.mode + 1) % modes.length
}
}}
onPointerOver={(e) => {
e.stopPropagation()
setHovered(true)
}}
onPointerOut={() => setHovered(false)}
name={name}
geometry={nodes[name].geometry}
material={nodes[name].material}
material-color={snap.current === name ? '#ff6080' : 'white'}
{...props}
dispose={null}
/>
)
}

function Controls () {
// Get notified on changes to state
const snap = useSnapshot(state)
const scene = useThree((state) => state.scene)
return (
<>
{/* As of drei@7.13 transform-controls can refer to the target by children, or the object prop */}
{snap.current && <TransformControls object={scene.getObjectByName(snap.current)} mode={modes[snap.mode]} />}
{/* makeDefault makes the controls known to r3f, now transform-controls can auto-disable them when active */}
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 1.75} />
</>
)
}

export default function Home () {
return (
<Canvas camera={{ position: [0, -10, 80], fov: 50 }} dpr={[1, 2]}>
<pointLight position={[100, 100, 100]} intensity={0.8} />
<hemisphereLight color='#ffffff' groundColor='#b9b9b9' position={[-7, 25, 13]} intensity={0.85} />
<Suspense fallback={null}>
<group position={[0, 10, 0]}>
<Model name='Curly' position={[1, -11, -20]} rotation={[2, 0, -0]} />
<Model name='DNA' position={[20, 0, -17]} rotation={[1, 1, -2]} />
<Model name='Headphones' position={[20, 2, 4]} rotation={[1, 0, -1]} />
<Model name='Notebook' position={[-21, -15, -13]} rotation={[2, 0, 1]} />
<Model name='Rocket003' position={[18, 15, -25]} rotation={[1, 1, 0]} />
<Model name='Roundcube001' position={[-25, -4, 5]} rotation={[1, 0, 0]} scale={0.5} />
<Model name='Table' position={[1, -4, -28]} rotation={[1, 0, -1]} scale={0.5} />
<Model name='VR_Headset' position={[7, -15, 28]} rotation={[1, 0, -1]} scale={5} />
<Model name='Zeppelin' position={[-20, 10, 10]} rotation={[3, -1, 3]} scale={0.005} />
<ContactShadows rotation-x={Math.PI / 2} position={[0, -35, 0]} opacity={0.25} width={200} height={200} blur={1} far={50} />
</group>
</Suspense>
<Controls />
</Canvas>
)
}

截图拍照

当需要预览图时,生成出来的会更方便。 两种方式提供,第一种直接截屏当前状态,第二种新摄像头

const { gl, scene, camera } = useThree()

function doPhoto1() {
gl.render(scene, camera)
return gl.domElement.toDataURL()
}
function doPhotot2() {
const camera2 = new THREE.PerspectiveCamera(75, 1, 0.01, 1000)
camera2.copy(camera)
camera2.position.set(.6, .6, .6)
gl.render(scene, camera2)
return gl.domElement.toDataURL('image/jpeg', .5)
}

如何改色

最复杂的留到最后,前面的部分基本都是应用层,只需要对 api 足够了解就能做出很多案例。这部分将需要 自定义 Class,改写官方实现。这里有两种方式,一种是改写加载类,一种是提取后自己组装

还记得 const object = useLoader(OBJLoader, src) 吗
OBJLoader 是官方的类 examples/jsm/loaders/OBJLoader.js
其中parse 方法 this.materials.create( sourceMaterial.name ) 就是关键
所以我们可以定义一个 MyOBJLoader 然后改写 parse
// 另一种方式,维护一个 mapping 表格,然后使用 mesh
export function useMcc (metaTable) {
useEffect(async () => {
for (const unitOne of metaTable) {
const { objName, mtlName, mtlId, mtlUdfEnable, mtlUdfUrl } = unitOne
// const name = Math.random().toString(36).slice(-6)
const name = `${objName}-${mtlName}`
if (mtlId) {
mccObjects[name] = await loadByMid(mtlId, mtlUdfEnable, mtlUdfUrl)
} else {
mccObjects[name] = new THREE.MeshStandardMaterial({ color: str2rgb(name) })
}
}
setMccObjects({ ...mccObjects })
}, [metaTable])

return { mccObjects }
}
export function useMetaTable (mccObjects, object) {
const [tb, set] = useState([])

useEffect(() => {
if (!mccObjects) return
// console.log('mccObjects', mccObjects)

const ret = []
for (let mesh of object.children) {
ret.push({
uuid: mesh.uuid,
geometry: mesh.geometry,
material: mesh.material.length
? mesh.material.map(v => get(mccObjects, mesh.name, v.name))
: get(mccObjects, mesh.name, mesh.material.name),
})
}
set(ret)
}, [mccObjects])

function get (arr, objName, mtlName) {
const name = `${objName}-${mtlName}`
return arr[name]
}

return tb
}

const { mccObjects } = useMcc(meta)
const metaTable = useMetaTable(mccObjects, object)
<group position={objPos}>
{metaTable.map(v => (
<mesh key={v.uuid} geometry={v.geometry} material={v.material} />
))}
</group>

总结

这些 demo 覆盖了很多场景,有疑问欢迎交流。所有代码随意使用,转发请带原文。

· One min read
FROM python:3.10.0-slim

RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \
&& sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list

RUN mkdir ~/.pip \
&& echo '[global]\n \
trusted-host=mirrors.aliyun.com\n \
index-url=https://mirrors.aliyun.com/pypi/simple\n \
' > ~/.pip/pip.conf

RUN apt-get update -y \
&& apt-get install -y gcc curl \
&& apt-get install -y --no-install-recommends vim tree \
&& rm -rf /var/lib/apt/lists/*


RUN curl -s -L -o ~/rocketmq-client-cpp-2.0.0.amd64.deb \
'https://github.com.cnpmjs.org/apache/rocketmq-client-cpp/releases/download/2.0.0/rocketmq-client-cpp-2.0.0.amd64.deb' \
&& dpkg -i ~/rocketmq-client-cpp-2.0.0.amd64.deb \
&& rm -f ~/rocketmq-client-cpp-2.0.0.amd64.deb

ADD https://gfdcc-production-profile.oss-cn-shanghai.aliyuncs.com/profile/fonts/SourceHanSansCN-Normal.ttf /root/.fonts/
ADD https://gfdcc-production-profile.oss-cn-shanghai.aliyuncs.com/profile/fonts/SourceHanSansCN-Bold.ttf /root/.fonts/

RUN pip install --no-cache-dir --default-timeout=600 gunicorn==20.1.0 numpy==1.21.4 CPython

WORKDIR /www/backend-gim

COPY src/backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# todo
#RUN apt-get -y --purge remove vim vim-runtime vim-common xxd cpp-8
#RUN apt-get autoremove -y

CMD ["gunicorn", "run:app", "-c", "./gunicorn.conf.py"]

# docker build -f __cicd__/gimc.rt.Dockerfile -t registry.cn-shanghai.aliyuncs.com/digital-web/gimc-rt:20211203-1148 .

· 5 min read

https://blog.csdn.net/wolanx/article/details/122857729

Intro

免费个人博客 的教程非常的 ,但大多面向 纯小白,反而对有一定基础的同学显得 落后 + 啰嗦,本文整合目前已知最好的方式,最快捷优雅的搞定一个博客,并给出 常见错误

知识点

  • hugo 的安装及使用不是本文重点,其实同理 hexo
  • git github 默认已掌握
  • github page 生成可访问的页面的 灵魂 所在
  • github action 自动生成上一步

完整链路解析

  • git commit 提交
  • 触发 github action .github/workflows/my-pipeline.yml 并满足设置中的 on 条件
  • 触发 pipeline 中的 steps
    • checkout:相当与 git clone,并且后续操作具有 github 完整权限,可以通过 permissions 设置
    • setup hugo:准备构建要求,安装对应版本,注意是否需要 extended
    • build:构建出静态文件,并输出到 public 文件夹
    • deploy:该插件来自 插件市场
      • 自动创建分支 gh-pages
      • 自动 copy public 到新分支
      • 自动提交
      • 自动生成 CNAME 文件,根据 cname 设置,想要 自定义域名 的注意这里了
  • 打开 https://github.com/{你的名字}/{你的仓库}/settings/pages(后续步骤只需要一次)
    • Source 选择 gh-pages ,文件夹: 默认 / (root) ,并 save
    • 注意上方提示 Your site is ready to be published at https://xxx.github.io/xxx/
    • 将域名部分做 解析
    • Custom domain 设置 自己的域名
    • Enforce HTTPS 点一下,然后等一会

步骤1:hugo github

步骤1.1: 创建 仓库 & 初始化 hugo

hugo github 的基本操作不是本文重点,忽略

步骤1.2: 创建 .gitmodules 文件

hugo 的 主题 themes 是通过 git 的 sub modules 实现,而 github 上 git 会自动根据 .gitmodules clone 子项目。没有 .gitmodules 文件会导致构建失败。 比如我的主题使用的是 hugo-book(推荐),那么配置如下

[submodule "themes/hugo-book"]
path = themes/hugo-book
url = https://github.com/alex-shpak/hugo-book

步骤2:github action 自动生成

官方默认的 Jekyll 其实是会根据分支 自动 构建发布的,但如果自己魔改使用 hugo | hexo 这类软件以后就不会自动,需要使用 action 功能,而 action 其实就是 github 的 pipeline版本,使用只需要一个文件 .github/workflows/my-pipeline.yml。其中可能需要改的:

  • main 分支 根据实际情况改一下
  • cname 最后一行 如果要使用自定义域名功能

步骤2.1: 创建 .github/workflows/my-pipeline.yml

name: my-pipeline

on:
push:
tags:
- '*'
branches:
- main

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build:
runs-on: ubuntu-latest
# permissions:
# contents: read
# packages: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

steps:
- name: checkout
uses: actions/checkout@v2
with:
submodules: true # Fetch Hugo themes (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod

- name: setup hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.92.0'
extended: true

- name: build
run: hugo --minify

- name: deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.ref == 'refs/heads/main' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
# cname: wolanx.com # 重点 !!!

步骤3:github page 页面生成

打开 https://github.com/{你的名字}/{你的仓库}/settings/pages , 设置

在这里插入图片描述

  • 没有 pages 功能:只有公开项目才有,私有 项目不支持
  • gh-pages 分支不存在:没有成功触发 action,成功触发,会自动生成分支 gh-pages,并将静态资源保存在 gh-pages 上
  • cname 一直被重置:.github/workflows/my-pipeline.yml 最后一行没有设置好
  • 域名设置没用:需要先做 域名解析,如阿里云,参考如下,记录值 是 github page 页面上给出的值的域名部分,!!!域名部分!!!域名部分

在这里插入图片描述

源码参考

总结

2022年了,很多原本复杂的东西,都已经变得非常的容易获得,但网上的文章常年不升级。根据本文方便你更好的白嫖到一个免费博客,如果需要支持自定义域名,阿里云上购买也不到100RMB。

· 2 min read

https://blog.csdn.net/wolanx/article/details/122733747

Intro

现在很多主流日志系统推崇 logfmt 格式,但是 python 中配套的不多,这边给个参考

日志大概长这样

# log.info("haha")
time="2022-01-28T17:00:52+0800" type=default level=info method="a.py:82" msg="haha"
# log.warning("no access")
time="2022-01-28T17:00:52+0800" type=default level=warning method="a.py:83" msg="no access"

实现过程

  • PiiLogger 继承 logging.Logger
    • 绑定自定义的 formatter
    • 清空原有 handler 否则会重复输出
    • 把 formatter 注册给 handler
    • hook 外层 变量 (如:每条log带上web请求的uuid)
  • PiiLoggerFormatter 继承 logging.Formatter

外层变量的使用

def getUUid():
v = None
if has_request_context(): # 判断 flask web 的生命周期下
v = Pii.app.get("uuid", "") # 根据自己业务写

return {"uuid": v}

log = PiiLogger.manager.getLogger("default")
log.withFormatter(getUUid)
log.info("haha")
# time="2022-01-29T10:38:21+0800" type=default level=info method="views.request_after" uuid="860ea870-80ac-11ec-a366-1eaadecc49e8" msg="haha"

完整代码

import logging
import numbers
from json.encoder import JSONEncoder
from typing import Any


class PiiLogger(logging.Logger):
def __init__(self, name: str, level=logging.NOTSET) -> None:
super().__init__(name, level)

self.setLevel(logging.INFO)
self.root.handlers.clear()

ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
self.formatter = PiiLoggerFormatter(
fmt='time="%(asctime)s" type=%(name)s level=%(levelname)s method="%(method)s"',
datefmt="%Y-%m-%dT%H:%M:%S%z",
)
ch.setFormatter(self.formatter)

self.addHandler(ch)

def withFields(self, ret):
print(ret)
return self

def withFormatter(self, func):
self.formatter.setExt(func)


class PiiLoggerFormatter(logging.Formatter):
ext: Any = None

def format(self, record):
if record.funcName == "<module>":
method = f"{record.filename}:{record.lineno}"
else:
method = f"{record.module}.{record.funcName}"

record.__setattr__("method", method)
record.levelname = record.levelname.lower()
msg = JSONEncoder().encode(str(record.msg))

ret = super().format(record)

if self.ext:
mor = logfmt(self.ext())
if mor:
ret += " " + mor

return f"{ret} msg={msg}"

def setExt(self, func):
self.ext = func


def logfmt(extra):
outarr = []
for k, v in extra.items():
if v is None:
outarr.append("%s=" % k)
continue

if isinstance(v, bool):
v = "true" if v else "false"
elif isinstance(v, numbers.Number):
pass
else:
if isinstance(v, (dict, object)):
v = str(v)
v = '"%s"' % v.replace('"', '\\"')
outarr.append("%s=%s" % (k, v))
return " ".join(outarr)


PiiLogger.manager.setLoggerClass(PiiLogger)


if __name__ == "__main__":
# use
log = PiiLogger.manager.getLogger("default")
log.info("haha")
log.warning("no access")

· 4 min read

https://blog.csdn.net/wolanx/article/details/122828185

Intro

项目中遇到需要 导出统计报表 等业务时,通常需要 pdf 格式。python 中比较有名的就是 reportlab 。 这边通过几个小 demo 快速演示常用 api。所有功能点 源码 都在 使用场景

一句话了解:跟 css 差不多,就是不断地对每样东西设置 style,然后把 style 和内容绑定。

功能点

  • 生成
    • 文件: 先 SimpleDocTemplate('xxx.pdf'),然后 build
    • 流文件:先 io.BytesIO() 生成句柄,然后同理
  • 曲线图 LinePlot
  • 饼图 Pie
  • 文字 Paragraph
    • fontSize 字体大小 推荐 14
    • 加粗 <b>xxx</b> 使用的是 html 的方式,字体自动实现
    • firstLineIndent 首行缩进 推荐 2 * fontSize
    • leading 行间距 推荐 1.5 * fontSize
    • fontName 默认中文会变成 ■
      • 下载 .ttf 文件 至少2个 【常规】【加粗】
      • 注册字体 pdfmetrics.registerFont 【常规】请用原名,方便加粗的实现
      • 注册字体库 registerFontFamily("HanSans", normal="HanSans", bold="HanSans-Bold")

其他 api 自行摸索,但基本离不开 css 那种理念。官网并没有常规文档的那种 md 模式,而是完全写在了 pdf 里,玩家需要自己去 pdf 里像查字典一样去找。官方文档

预览

在这里插入图片描述

完整代码

import os

from reportlab.graphics.charts.lineplots import LinePlot
from reportlab.graphics.charts.piecharts import Pie
from reportlab.graphics.shapes import Drawing
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.pdfmetrics import registerFontFamily
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import Paragraph

home = os.path.expanduser("~")

try:
pdfmetrics.registerFont(TTFont("HanSans", f"{home}/.fonts/SourceHanSansCN-Normal.ttf"))
pdfmetrics.registerFont(TTFont("HanSans-Bold", f"{home}/.fonts/SourceHanSansCN-Bold.ttf"))
registerFontFamily("HanSans", normal="HanSans", bold="HanSans-Bold")
FONT_NAME = "HanSans"
except:
FONT_NAME = "Helvetica"


class MyCSS:
h3 = ParagraphStyle(name="h3", fontName=FONT_NAME, fontSize=14, leading=21, alignment=1)
p = ParagraphStyle(name="p", fontName=FONT_NAME, fontSize=12, leading=18, firstLineIndent=24)


class PiiPdf:
@classmethod
def doH3(cls, text: str):
return Paragraph(text, MyCSS.h3)

@classmethod
def doP(cls, text: str):
return Paragraph(text, MyCSS.p)

@classmethod
def doLine(cls):
drawing = Drawing(500, 220)
line = LinePlot()
line.x = 50
line.y = 50
line.height = 125
line.width = 300
line.lines[0].strokeColor = colors.blue
line.lines[1].strokeColor = colors.red
line.lines[2].strokeColor = colors.green
line.data = [((0, 50), (100, 100), (200, 200), (250, 210), (300, 300), (400, 800))]

drawing.add(line)
return drawing

@classmethod
def doChart(cls, data):
drawing = Drawing(width=500, height=200)
pie = Pie()
pie.x = 150
pie.y = 65
pie.sideLabels = False
pie.labels = [letter for letter in "abcdefg"]
pie.data = data # list(range(15, 105, 15))
pie.slices.strokeWidth = 0.5

drawing.add(pie)
return drawing

使用场景1:生成文件

doc = SimpleDocTemplate("Hello.pdf")

p = PiiPdf()
doc.build([
p.doH3("<b>水泵能源消耗简报</b>"),
p.doH3("<b>2021.12.1 ~ 2021.12.31</b>"),
p.doP("该月接入能耗管理系统水泵系统 xx 套,水泵 x 台。"),
p.doP("本月最大总功率 xx kW,环比上月增加 xx %,平均功率 xx kW;环比上月增加 xx %。"),
p.doP("功率消耗趋势图:"),
p.doLine(),
p.doP("本月总能耗 xxx kWh,环比上月增加 xx %。"),
p.doP("分水泵能耗统计:"),
p.doChart(list(range(15, 105, 20))),
p.doP("其中能耗最高的水泵为:xxx, 环比上月增加 xxx kWh,xx %。"),
])

使用场景2:web(flask)

@Controller.get("/api/pdf")
def api_hub_energy_pdf():
buffer = io.BytesIO() # 重点 起一个 io
doc = SimpleDocTemplate(buffer)

p = PiiPdf()
doc.build([
p.doH3("<b>2021.12.1 ~ 2021.12.31</b>"),
])

buffer.seek(0)
return Response( # io 形式返回
buffer,
mimetype="application/pdf",
headers={"Content-disposition": "inline; filename=test.pdf"},
)

· 5 min read

真正厉害的工程师,都在修炼这5种底层能力 https://mp.weixin.qq.com/s/HpFS3jbmHPLKtdyNmflJZQ

6个职级

职级一共分六级

  • E1 - 助理工程师(Associate Engineer)
  • E2 - 工程师(Engineer)
  • E3 - 高级工程师(Senior Engineer)
  • E4 - 资深工程师(Staff Engineer)
  • E5 - 高级资深工程师(Senior Staff Engineer)
  • E6 - 首席工程师(Principal Engineer)

5个关键区

评估的纬度共分5个关键区(Key area),关键区再细分为15个价值(Value)维度,价值维度再细分为27个胜任力(Competency)纬度。

  • 技术技能(Technical skills)
  • 交付(Delivery)
  • 反馈、沟通和协作(Feedback,Communication,Collaboration)
  • 领导力(Leadership)
  • 战略性影响(Strategic Impact)

影响力范围

各个工程级别的最主要差异在所能hold住的工作的范围(scope),或者说影响力范围,其中: E1~E3属于独立贡献者(individual contributor),工作范围有限,偏任务执行(Execution of Work)。 E4~E6能够善用人力(一般要带团队)和产品等杠杆进行规模化的生产(Utilizing skills to scale and generate leverage)。 职级越高越偏向战略方向、理念和文化建设。

  • E1:任务范围内
  • E2:项目范围内
  • E3:小组/团队内
  • E4:整个团队范围
  • E5:几个相关团队范围
  • E6:整个技术部门甚至公司

职级和胜任力评估框架

  • 技术技能
    • 质量和测试-写代码
    • 质量和测试-测试
    • 质量和测试-调试
    • 质量和测试-可观测性
    • 软件设计和架构-理解领域(domain)
    • 软件设计和架构-软件架构
    • 软件设计和架构-安全
  • 交付
    • 增量价值交付-任务分解
    • 增量价值交付-优先级和依赖分析
    • 增量价值交付-应对不确定性
    • 自我组织-可信赖和责任感
    • 自我组织-经济思维
  • 反馈、沟通和协作
    • 反馈-提供反馈
    • 反馈-寻求和接收反馈
    • 沟通-有效沟通
    • 沟通-知识分享
    • 协作-团队合作
    • 协作-关系建设
    • 协作-解决分歧
  • 领导力
    • 推进对齐
    • 流程思考
    • 协调促进
    • 教导
  • 战略性影响
    • 业务敏锐和战略-业务敏锐
    • 业务敏锐和战略-战略工作
    • 业务敏锐和战略-产品思维

Demo

软件设计和架构-软件架构:

职级胜任力
E1对服务化架构有总体认识,在此基础上能够设计基本的服务/模块,同时尽量避免冗余代码/功能,减少对接口的不兼容变更。
E2设计的服务要和总体服务化架构对齐(align with)。
E3设计的服务/系统和总体架构始终对齐。能高效地利用抽象、模块化和重用机制。
E4使用行业沉淀下来的成熟的设计模式来架构服务和系统,让团队可以增量和自治的开发,并考虑未来的扩展性。考虑未来的可能用例场景,在做设计决策时,以最小化未来变更成本为主要目标(也就是架构要灵活适应未来的变化)。
E5在多个团队之间宣导能支持增量和自治开发的,并且能支持未来扩展的架构文化。指导多个团队考虑未来的可能用例场景,在做设计决策时,以最小化未来的变更成本为主要目标。
E6在整个组织内宣导能支持增量和自治开发的,并且能支持未来扩展的架构文化。指导组织内的所有团队考虑未来的可能用例场景,在做设计决策时,以最小化未来的变更成本为目标。

· One min read

Intro

Java 诊断工具 Arthas 入门教程 https://start.aliyun.com/course?id=qDlgqpBT 进阶 命令 https://arthas.aliyun.com/doc/advanced-use.html

Install

wget https://arthas.aliyun.com/arthas-boot.jar

use java > 8 when jre tool.java

Start

java -jar arthas-boot.jar

# input pid

help

dashboard

thread 1
thread 1 | grep 'main('

sc -d *MathGame # 查找JVM里已加载的类

jad demo.MathGame # 反编译代码

watch demo.MathGame primeFactors returnObj # 查看函数的参数/返回值/异常信息

· One min read

Intro

重学算法,之前停留在学习基本思想比如 leetcode#704,但是问题衍生出来的实际问题直接看不懂 leetcode#475。 重新以实际题的方式去了解

二分

  • 单调性
  • 000111找突变边界
  • 使用主动与被动不同的视角看

动态规划

· One min read

Intro

本站使用 博客系统hugo,搭配主题hugo-book

Start

# 本地测试
hugo server -w

# add
hugo new posts/xxx.md

Guide

Intro
Install
Start

Packages
Contributing

About
License

Test

{{< tabs "uniqueid" >}} {{< tab "MacOS" >}} # MacOS Content {{< /tab >}} {{< tab "Linux" >}} # Linux Content {{< /tab >}} {{< tab "Windows" >}} # Windows Content {{< /tab >}} {{< /tabs >}}

百度统计

自定义 layouts/partials/docs/inject/footer.html