美林时钟 | 库存周期 | 经济表现 |
复苏期 | 被动去库存 | 经济↑,通货膨胀↓,商场需求↑ 生产未及时跟进,库存↓ |
过热期 | 主动补库存 | 经济↑,通货膨胀↑,市场需求↑ 生产未及时跟进,库存↑ |
滞涨期 | 被动补库存 | 经济↓,通货膨胀↑,市场需求↓ 生产未及时跟进,库存↑ |
衰退期 | 主动去库存 | 经济↓,通货膨胀↓,市场需求↓ 生产未及时跟进,库存↓ |

美林时钟 | 库存周期 | 经济表现 |
复苏期 | 被动去库存 | 经济↑,通货膨胀↓,商场需求↑ 生产未及时跟进,库存↓ |
过热期 | 主动补库存 | 经济↑,通货膨胀↑,市场需求↑ 生产未及时跟进,库存↑ |
滞涨期 | 被动补库存 | 经济↓,通货膨胀↑,市场需求↓ 生产未及时跟进,库存↑ |
衰退期 | 主动去库存 | 经济↓,通货膨胀↓,市场需求↓ 生产未及时跟进,库存↓ |
PHP内置服务器的路由脚本可以参考这个链接:使用PHP内置服务器运行WORDPRESS
FROM php:8.1.27-alpine
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
&& mkdir /code
&& docker-php-ext-install mysqli
CMD [ "php", "-S", "0.0.0.0:8080", "-t", "/code", "/code/router.php" ]
version: "3"
services:
wordpress:
build: .
volumes:
- /Users/ianzhi/Code/php/wordpress:/code
restart: always
ports:
- 8080:8080
mysql:
image: mysql:8.0.36
environment:
MYSQL_ROOT_PASSWORD: 'root'
expose:
- 3306
ports:
- 3306:3306
背景:一个记录表,类似日志的信息,查询大量集中在某个用户个人的数据,分区需要尽量保证一个人的数据在一个分区里。因此采用通过user_id进行hash分区的方式。
软著申请在中国版权保护中心,记录一下具体的要求细节,更具体的信息参考:计算机软件著作权登记指南。
函数计算有冷启动的问题,如果启用VPC,还有VPC的启动时间,为了避免冷启动问题,可以通过预热程序来避免函数实例被销毁。此预热程序示例使用Cloudflare Worker,正好同时用预热程序支持Wordpress的定时任务。
xxx.xxxx.xxx {
tls youremail@yourmailserver
root * /path/to/wordpress
# GZIP和FPM配置
encode gzip
file_server
php_fastcgi php:9000
# 静态文件配置
@static_files {
path_regexp .(?:css|js|woff2?|svg|gif|map|png|jpg|webp|gif|jpeg|mp4|mp3|wav|mov|heic)
}
header @static_files {
Cache-Control "public, max-age=15778463"
X-Robots-Tag "none"
X-Permitted-Cross-Domain-Policies "none"
X-Frame-Options "SAMEORIGIN"
X-Download-Options "noopen"
X-Content-Type-Options "nosniff"
# Referrer-Policy "no-referrer"
}
# 禁止访问的目录/文件
@disallowed {
#path /wp-cron.php
#path /xmlrpc.php
path *.sql
path /wp-content/uploads/*.php
path /wp-content/uploads/*.html
path /wp-content/debug.log
}
rewrite @disallowed =404
}
dd if=/dev/zero of=/swap bs=1G count=2
# 如果出现Killed,一般是可用内存不足,可以尝试以下
dd if=/dev/zero of=/swap bs=1M count=2048
mkswap /swap
swapon /swap
echo '/swap none swap defualts 0 0' >> /etc/fstab
echo 'vm.swappiness=0'>> /etc/sysctl.conf
sysctl -p
在Debian系统上(红帽系Linux发行版应该也是一样的),systemd-journald
服务负责管理 journal
日志。这些日志可以占用大量的磁盘空间,特别是当系统持续运行并且产生大量日志条目时。可以配置 systemd-journald
的日志保留策略来自动清理这些日志。
一、配置项及意义
配置项 | 意义 | 建议 |
innodb_buffer_pool_size | 控制 InnoDB 存储引擎的内存缓存池大小 | 总内存(非专用服务器可以使用希望分配给数据库的内存量)的 50% 到 80% |
innodb_buffer_pool_chunk_size | InnoDB 缓冲池大小调整操作的块大小,默认值为 128MB | innodb_buffer_pool_size = innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances |
innodb_buffer_pool_instances | 决定了 InnoDB 缓冲池(Buffer Pool)的实例数量 | 设置为 CPU 核心数的较小倍数,超过 1GB 时可以适量增加,最高不超过1000 |
innodb_buffer_pool_in_core_file | 从核心文件中排除缓冲池页面,参考15.8.3.7 从核心文件中排除缓冲池页面_MySQL 8.0 参考手册 | off |
innodb_flush_log_at_trx_commit | 定义了事务提交时,InnoDB 如何处理未刷入(flush)的重做日志(redo log),默认值为1,设置为0/2,在操作系统崩溃或断电时可能会丢失最后一秒的事务数据 | 对于需要高可靠性和数据完整性的系统设置为 1,对于性能要求更高,且可以接受一定数据丢失风险的系统设置为 0 或 2。 |
innodb_change_buffering | 控制着 InnoDB 执行变更缓冲(Change Buffering)的程度 | none |
innodb_log_buffer_size | 设置InnoDB日志缓冲区的大小 | 64m |
innodb_flush_method | 决定了 InnoDB 如何将数据和日志刷新(flush)到磁盘 | O_DIRECT |
innodb_purge_threads | 定义了用于回收不再需要的 UNDO 日志的 Purge 线程的数量 | cpu > 4 ? 4 : 1 |
最近公司的应用上架APP Store过程中一直遇到审核问题,记录一下遇到的问题,方便后续其他应用开发避坑。
## 1、与安卓通用的审核条件
>>> [APP分发前的准备工作](https://www.dnote.cn/posts/899)
## 2、虚拟支付
– 所有的虚拟商品直接使用苹果的内购方式付款,避免后续改造的问题(需要给苹果30%抽成)
– 提前考虑宣传物料和价格在不同货币区域的展示,或者避免物料中包含价格
– 至少审核阶段,不要在应用界面里放跳转到其他应用的链接,苹果审核会认为这涉及到其他支付方式
## 3、强制登录问题
微信小程序也是一样的要求,所以最好设计阶段就开始考虑这个问题。
– 不需要用户信息的数据不设计为强制鉴权
– 将引导用户登录放到用户进行必须鉴权的操作时
1. 调整交换分区使用配置
vim /etc/sysctl.conf
vm.swappiness = 40
2. 让配置生效
sysctl -p
一、全局配置
name: lnmp
services:
caddy:
image: caddy:latest
volumes:
- ./www:/var/www/html
- ./caddy/etc:/etc/caddy
- ./caddy/data:/data
- ./caddy/config:/config
ports:
- 80:80
- 443:443/tcp
- 443:443/udp
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: 3
restart: always
redis:
image: redis:latest
volumes:
- ./redis/config:/etc/redis
- ./redis/data:/data
restart: always
command: /etc/redis/redis.conf
mysql:
image: mysql:latest
volumes:
- ./mysql/config:/etc/mysql
- ./mysql/data:/var/lib/mysql
- ./mysql/mysql-files:/var/lib/mysql-files
cap_add:
- SYS_NICE
security_opt:
- seccomp:unconfined
environment:
MYSQL_ROOT_PASSWORD: password
ports:
- 3306:3306
restart: always
php:
build: ./php
volumes:
- ./php/config:/usr/local/etc
- ./php/logs:/var/log/php
- ./www:/var/www/html
depends_on:
- caddy
- mysql
- redis
cap_add:
- SYS_PTRACE
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: 3
restart: always
imaginary:
image: nextcloud/aio-imaginary:latest
restart: always
command: -concurrency 2 -enable-url-source
environment:
- PORT=9000
1. PHP
PHP官方的镜像启用和安装的扩展比较少,直接使用会导致WordPress和Nextcloud的健康检查一堆信息,所以使用Dockerfile来基于官方镜像构建一个专用的镜像,PHP需要的扩展包括:
FROM registry.cn-beijing.aliyuncs.com/ianzhi/php:8.4-fpm-alpine
# 配置国内镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
# 安装构建扩展相关依赖
RUN apk add --no-cache --update --virtual .build-deps $PHPIZE_DEPS
# MySQL
RUN docker-php-ext-install pdo_mysql mysqli \
&& docker-php-ext-enable pdo_mysql mysqli
# 常用扩展
RUN docker-php-ext-install pcntl exif bcmath sysvsem \
&& docker-php-ext-enable opcache exif bcmath pcntl sysvsem
# apcu
RUN pecl install apcu && docker-php-ext-enable apcu
# zip扩展
RUN apk add --no-cache --update libzip=1.11.4-r0 libzip-dev=1.11.4-r0 unzip \
&& docker-php-ext-install zip \
&& docker-php-ext-enable zip
# redis
RUN pecl install https://pecl.php.net/get/redis-6.2.0.tgz \
&& docker-php-ext-enable redis
# intl
RUN apk add --no-cache --update icu icu-dev \
&& docker-php-ext-configure intl \
&& docker-php-ext-install intl \
&& docker-php-ext-enable intl
# imagick
RUN apk add --no-cache --update imagemagick-dev imagemagick-svg \
&& pecl install https://pecl.php.net/get/imagick-3.8.0.tgz \
&& docker-php-ext-enable imagick
# gd
RUN apk add --no-cache --update libpng libpng-dev libavif-dev libjpeg-turbo-dev freetype-dev freetype libjpeg-turbo libavif \
&& docker-php-ext-configure gd --with-freetype --with-jpeg --with-avif \
&& docker-php-ext-install gd \
&& docker-php-ext-enable gd
# ffmpeg nextcloud需要视频转码时启用
# RUN apk add --no-cache ffmpeg
# gmp nextcloud使用加密时使用
# RUN apk add --no-cache --update gmp-dev \
# && docker-php-ext-install gmp \
# && docker-php-ext-enable gmp
# pgsql
# RUN apk add --no-cache --update libpq-dev postgresql-dev \
# && docker-php-ext-install pdo_pgsql \
# && docker-php-ext-enable pdo_pgsql
# 删除构建依赖
RUN apk del --no-network .build-deps
# 配置文件
RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
未来的六大经济环境变化:
通缩转为通胀
利率由负转正
逆全球化
基础能源资料暴涨
生产成本提高
赚钱效益减弱
股指 | 参考指数 | 低点 | 高点 | 卖出点位 | 周期 |
A股 | 沪深300市盈率中位数 | 16-19 | 40 | 30-33 | 4年左右 |
港股 | 恒生指数市盈率 | 10 | 20 | 16-17 | 5-6年左右 |
美股 | 标普500市盈率 | 12-14 | 27-30 | 27-30 | 10年 |
用Apache比较少,今天正好碰到,记录一下,本质就是修改响应头信息。
# 加载模块
# LoadModule headers_module modules/mod_headers.so
2. 修改vhost配置
<Directory /www/web/yuming.com/public_html/>
Options FollowSymLinks
AllowOverride All
Require all granted
Header set Access-Control-Allow-Origin *
# 或者指定具体的主机名
# Header set Access-Control-Allow-Origin http://example.com
# Header set Access-Control-Allow-Origin http://localhost:8080
</Directory>
国内提供Oauth认证服务的好少,使用第三方登录降低了认证服务的复杂度,也降低了用户的决策难度,这里用Gitee作为一个示例。应该适用于大多数的Web应用,只有客户端获取authorization_code部分的代码可能不一致。Parse官方文档里的自定义认证写的语焉不详,折腾了好久。
“`typescript
const gitee = require(‘./auth/gitee’);
{
auth: {
gitee: {
module: gitee, // OR object,
client_id: “”, // Gitee第三方应用ID
client_secret: “”, // Gitee第三方应用的密钥
}
}
}
“`
主要需要实现两个函数:validateAuthData和validateAppId。
const qs = require('querystring')
const Parse = require('parse/node')
async function getAuthData(code, redirect_uri, options) {
const token = await codeToToken(code, redirect_uri, options)
const user = await userInfo(token.token_type, token.access_token)
return {
id: user.id,
access_token: token.access_token
}
}
async function codeToToken(code, redirect_uri, options) {
const url = `https://gitee.com/oauth/token`
const query = qs.stringify({
grant_type: 'authorization_code',
code,
redirect_uri,
client_id: options.client_id,
client_secret: options.client_secret
})
const res = await fetch(`${url}?${query}`, { method: 'POST' })
return await res.json()
}
async function userInfo(type, token) {
const url = 'https://gitee.com/api/v5/user'
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `${type} ${token}`
}
})
return await res.json()
}
// Returns a promise that fulfills if this user id is valid.
async function validateAuthData(authData, options) {
if (!authData.id) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '缺少id字段');
}
if (!authData.access_token) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '缺少access_token字段');
}
const user = await userInfo('bearer', authData.access_token)
if (user && String(user.id) === String(authData.id)) {
return
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'authData is invalid for this user.');
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId(appIds, authData, options) {
console.log('validateAppId:', appIds, authData, options)
return Promise.resolve();
}
module.exports = {
validateAuthData,
validateAppId,
getAuthData
}
const gitee = require('../auth/gitee')
const config = require('./config')
Parse.Cloud.define("giteeAuthData", async (request) => {
return await gitee.getAuthData(request.params.code, request.params.redirect_uri, config.auth.gitee)
})
async function login() {
const redirectUrl = chrome.identity.getRedirectURL()
logger.log('redirect_url:', redirectUrl)
// 获取认证码
const authUrl = await _oauthCode(redirectUrl)
if (authUrl === undefined) {
throw new Error('login failed')
}
const url = new URL(authUrl)
const code = url.searchParams.get('code')
if (code == null) {
throw new Error('login failed')
}
logger.log('code:', code)
// 获取认证信息
const authData = await _oauthData(code, redirectUrl)
user.value = await Parse.User.logInWith('gitee', {
authData
})
}
async function _oauthData(code: string, redirect_uri: string) {
const authData = await Parse.Cloud.run('giteeAuthData', {
code,
redirect_uri
})
logger.log('authData:', authData)
return authData
}
[1] Parse 自定义认证文档地址, Parse Server Guide | Parse (parseplatform.org)
[2] Gitee Oauth文档,Gitee OAuth 文档
本来想使用oauth来实现登录,但是国内提供oauth服务要么比较小众,要么居然收费的?传统的用户注册登录使用起来过于繁琐了,很容易把用户挡在最开始的地方,最后决定添加邮箱验证码登录。
Parse Platform的文档中提到了一个Parse.User.become()
方法,但是需要传递一个session token进去,翻了很多遍文档也没发现该如何获取这个session_token,最后在Github仓库的issue中搜到2023年Parse增加了一个loginAs
方法,可以通过传递一个userId来将用户登陆进系统,那么通过云函数和Parse.User.become()
就可以实现邮箱验证码登录了。
const VerifyCode = Parse.Object.extend('VerifyCode')
/**
* 发送邮件验证码
*/
Parse.Cloud.define('verifyCode', async (request) => {
const email = request.params.email
const code = generateRandomString(6)
// 记录验证码
const verifyCode = new VerifyCode()
verifyCode.set('email', email)
verifyCode.set('code', code)
verifyCode.set('expiredAt', new Date(Date.now() + 1000 * 60 * 5))
await verifyCode.save(null, { useMasterKey: true })
// 删除过期的验证码记录
const query = new Parse.Query(VerifyCode)
query.lessThan('expiredAt', new Date())
await Parse.Object.destroyAll(await query.find({ useMasterKey: true }), { useMasterKey: true })
// 发送邮件
await Parse.Cloud.sendEmail({
templateName: "verifyCode",
placeholders: { code },
recipient: email
});
}, {
fields: {
email: {
required: true,
type: String
}
},
requireUser: false
})
const VerifyCode = Parse.Object.extend('VerifyCode')
Parse.Cloud.define('login', async (request) => {
if (!request.params.email) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '缺少email字段');
}
if (!request.params.code) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '缺少code字段');
}
const query = new Parse.Query(Parse.User)
query.equalTo('email', request.params.email)
let user = await query.first({ useMasterKey: true })
if (user === undefined) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '用户不存在');
}
// 验证
const codeQuery = new Parse.Query(VerifyCode)
codeQuery.equalTo('email', request.params.email)
codeQuery.equalTo('code', request.params.code)
codeQuery.greaterThan('expiredAt', new Date())
const code = await codeQuery.first({ useMasterKey: true })
if (code === undefined) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '验证码已过期');
}
// 修改邮箱验证字段为验证通过状态
user.set('emailVerified', true)
user.save(null, { useMasterKey: true })
// 登录用户
user = await Parse.User.loginAs(user.id)
return user.getSessionToken()
}, {
fields: {
email: {
required: true,
type: String
},
code: {
required: true,
type: String
}
}
})
export async function emailLogin(email: string, code: string) {
const token = await Parse.Cloud.run('login', { email, code })
return await Parse.User.become(token)
}
[1] 设置当前用户,JavaScript 开发人员指南 |解析 (parseplatform.org)
[3] Parse Server API Mail Adapter,parse-server-api-mail-adapter – npm (npmjs.com)