2020-03-31 11:50:24

这世上的女人大概有很多种,但是我却对 Constance Seppala (From - Togo performed by Julianne Nicholson )这种情有独钟。


2020-3-27 19:55:48

人在家中坐,烦心的事儿从远方来,更要命的是这种事情无解。我就想平平静静的生活,做我自己喜欢的事情,真的就那么难吗?


2020-03-27 0:51

或许有人出生自带光环,然而大部分人起点都差不多。而你所不知道的是,行业知名大佬每晚学习到深夜总结提炼升华;过目不忘的人实际上有个日记本笔,事无巨细,无一所落;光鲜亮丽的职场小姐姐背后是无止境的加班,对客户堆笑和掉头发。每份工作都差不多,每个人都不容易。而我说的每一个惨字,都是感同身受,因为我和你是一样的人,我们过的是一样的生活。


2020-03-24 12:34:33

Sufferring makes great nation a uion and reveal the true nature of culture and belief, as well as catastrophe lacerat people's trust and corrupt souls of illed country.

一直以来,我们从小时候就接受的是唯物主义的哲学世界观,任何探究灵魂和自然之外的想法都会被身边的人钉在封建迷信的耻辱柱上,俨然唯物论点不可辩驳,绝对正确,任何有这种想法的怀疑权威的人都是我们的敌人,甚至我们必须从肉体上消灭这类敌人。假如有人说一切都是虚幻,万物皆你一人心之所化,你大概会想这人估计有病。又或者说万物皆一主所化,这大概也背离了当代中国人无神论的主流价值观,有这种想法的人大概也是脑子有问题。可是事实真的是这样吗?

相对论定义了宏观世界的运行规律,行星在恒星引力的作用下围绕恒星做圆周运动,多个行星公转则构成多个圆周轨道,而这种显而易见的运行规则告诉我们实际上星系成员彼此运行在一个由引力聚合而相互作用的平面上。而行星在这个引力平面上的下沉程度,影响这个行星的公转速度。引力的作用实际上可以抽象为一个二维平面。星系是这样,放大到宇宙也大概如此。又或者说,在地球上,根据一张包含光线信息的照片和当地大概时间,实际上可以计算出照片主体所在的大概位置,即经纬度信息。因此只要有了时间和二维平面的信息,是可以计算三维世界物体的相对空间位置。宇宙全息理论和弦理论告诉我们二维平面可以完全表达三维立体空间,除了上面的例子,我的理解是如果二维平面的信息和细节足够丰富,透过二维平面内元素排列、深浅(原子分布信息),是可以投影出一个甚至多个三维空间的。这也就是为什么可以用CAD平面图,来构建完整的3D模型的原因。那么如果我们生活的世界本质上是一个二维平面的三维投影,而这个本质也符合相对论对宏观宇宙的解释,那么是谁创造了这个二维平面呢?是否还有一个超脱于这个二维平面之外的另一个存在?这是一个有意思的问题。人们会将这个特定存在实体化具象化为神,这大概是人们对于这个问题的一种解答方式。中国自古以来就有“天道恢恢”的说法,我也认为我们身处的这个世界的运行规则早已经由更高级的实体确定了,而我们所创造的自然科学、哲学、神学等都是对这个规则从某一个角度的解释。至于这个规则到底是什么,创造这个规则的实体最终目的是什么,而这个实体又是什么样的,我本庸才,自然是想不通的。

2020的疫情并不让我意外,让我意外的这种确定性最终选择了瘟疫作为呈现方式而影响了千家万户。在家避疫之余,除了看书,思考这些毫无边际的问题,做些好玩的技术测试之外,剩下的就是看直播了。看直播除了看个好玩之外,我也在思考直播为代表的自媒体这种文化现象其背后所衍生的内涵。互联网基础设施的不断升级换代,催生了自媒体行业由不务正业的代名词,继而蓬勃发展到现在的时尚行业,成了社会各阶层人士消遣时间时快乐或者悲伤的源泉,也大有取代传统媒体的趋势。除了互联网基础设施的进步,我认为更重要的一个原因是新生代的推动。90后和00后作为时代的潮人,他们有着独特的认知模型、表现方式和价值体系,而新生代的这种独特个性和群体共性,在他们之后的下一代身上则会有更明显的表现,这种趋势正在积蓄力量并将最终打破以往教育所侧重对集体认同感的培养,对个性的抹杀,对思维的禁锢和对自我表达的漠视。照着这样的趋势发展下去,人的定义权终将回归到个人。而有意思的是,他们也是带动了老一代80后、70后甚至爷爷奶奶一辈加入了直播和自媒体的大军。群体的不断壮大,多种社会成分的不断融入,自媒体文化俨然成为了一种新的社会力量。这种力量正在潜移默化的影响着这个时代的最终走向。我不知该如何定义这种趋势和变化,因此暂且叫他灵性的觉醒。

现阶段的自媒体市场已经从原本的粗放爆炸式增长回归理性的区间,市场渐渐静下来了,这种变化本身反映的是饥渴的市场已经逐渐趋于饱和,开始沉淀清洗,而这种清洗是一种去伪存真回归价值的过程。以官宣为代表的某信和某博纵然可以继续传播谎言愚弄大众成为洗地的先锋军,以卖肉和搞笑为代表的某音也可以继续撒播娱乐的鸦片,而当人们一旦失去了对平台的信任和腻味了单调乏味的空虚内容,流量就再也回不来了。其兴也勃焉,其亡也乎焉。而对直播来说,obs stream一旦推上去了,观众所看到的就是一场即兴表演,很多时候,主播只能凭借自己的个性特长来满足自己的表现欲亦或满足观众老爷的观赏需求,一切都没有那么容易作假了。当然,直播卖肉、带货和唱见舞见对我来说,虽然我完全没有兴趣,且我也不爱看,但我誓死捍卫这类UP直播的权利。直播间生态则是另一个有意思的地方,弹幕即战场,形形色色无奇不有,一个小小的直播间就是一个社会的缩影。有唇枪舌箭你来我往艹爹骂娘继而到定好时间地点约起线下父子局,也有举例论证引经据典讲求是非的以理服人;有女装大佬搔首弄姿迎来众多白嫖看客一脸懵逼,也有国画大师妙笔生花粉丝送礼婉言相拒。千千万万个灵魂,千千万万种风采,你有你的口味,我有我的风格,多样性的繁荣催生了社会整体宽容度的不断冗余,在别国大多是宽容度培养多样性,而在天朝,则是多样性催生宽容度。但无论如何,我认为这也是一种时代的进步。且不说好与坏,这世上又哪里有绝对的好和绝对的坏呢。

有一日,在技术讨论群中和群友讨论疫情网课系统背后的技术实现,群友就说网课系统何其烂,他儿子上课多么无奈,顺道聊到小孩子上网课毫无兴趣只好用电脑玩游戏。我笑嘻嘻的问,你家孩子要拿电脑玩游戏你怎么办?他说我儿子要玩游戏,我就给他端茶倒水。我觉得这样的家长真的是个好榜样。一个在快乐中成长,既能好好学习又能好好玩游戏的孩子,能够吸收自然科学的知识,又能在游戏的同时锻炼自己的思维,学会情绪控制和自我约束,这样一个孩子才能形成完整的认知能力。有人说钱是一种风险抑制剂,而我认为认知则是创造这种风险抑制剂的车间。相反,我们的社会培养了太多具有理性知识而没有感情,或者感情泛滥到偏执而却缺乏理性思维的工具人,无法做到以出离自我的状态去利用逻辑的力量来对事物内部的各种成分力量做对比分析,进而得出合乎事实本身的结论。大多数的时候他们都是随波逐流,在他们眼中,跟他们观点看法不一致的人就是是错的,他人怀异而错,真理天下唯我。电脑游戏本身实际上是一种娱乐活动,与老一辈们的纸牌,麻将本质上是同一个东西,无非就是表现形式有所不同。他们之所以固执的认为玩游戏不好,是因为他们无法理解游戏本身,无法理解无法认同即为错。而一款好的游戏,是可以训练一个人的思维,增加知识和陶冶情操的。因此我时常告诫自己,我所不能理解的,不一定是错的;而我所信奉的,也不一定完全就是对的。我不能强加思维让别人接受我的想法,正如别人也不能用各种冠冕堂皇的理论缘由来给我洗脑。任何时候都要保持思维的独立性,对别人的观点要结合事实透过逻辑加以验证,才可以得出结论。己所欲强施于人和己所不欲施于人的性质是一样的。

安装 pipenv 依赖和包管理工具

brew install pipenv

切换 pip ( pypi 源为国内 USTC.EDU 中科大)

mkdir ~/.pip
cd .pip
vim pip.conf
# 填充以下内容
[global]
timeout = 60
index-url = https://mirrors.ustc.edu.cn/pypi/web/simple

建立项目目录

mkdir project-directory
cd project-directory
pipenv install
# ------------- 终端会反馈以下输出
# Creating a virtualenv for this project…
# Pipfile: /project-directory/Pipfile
# Using /usr/local/opt/python/bin/python3.7 (3.7.4) to create virtualenv…
# -------------

# 修改项目的 pypi 源地址为国内
vim Pipefile

#修改为以下设置

[[source]]
name = "pypi"
url = "https://mirrors.ustc.edu.cn/pypi/web/simple"
verify_ssl = true

[dev-packages]

[packages]
pymysql = "*"
pathlib = "*"

[requires]
python_version = "3.7

进入子 shell 环境

pipenv shell

注意: 如果不进入子 shell,则 pipenv 会调用系统安装的默认 python 的 pip 来安装库,比如 OSX 默认就是 2.7版本,这样就会爆出一堆的依赖错误问题。

进入子 shell 之后便可以方便使用 pipenv install 来管理项目使用的模块了, 比如安装 scrapy 的命令就是 pipenv install scrapy

其实一直一来早都想把环境切换到 Nginx ,因为即使 Apache 运行在 event 模式下,受限于自身的架构,其性能的表现和资源占用的情况依旧无法和 Nginx 站在同一基准线上。而促使我做这件事的原因很简单,Let's Encrypt的证书过期了,厌倦了每三个月去 manual renew 证书一次,遂将证书的管理转到 Acme.sh, 后者支持阿里云的 DNS api,可以方便的自动 renew 证书。证书的生成和配置很简单,然而却出现了另外一个问题,Apache 无法支持 ACME 获得证书,这里大概是因为证书使用了另外一种加密方式,而 Apache 无法 Cipher。我没有纠结于证书的类型和加密方式,对我来说自动续签更加重要。既然 Apache 不行,正好借此机会换到 Nginx。

由于以前的 LAMP 早已安装和配置好,这里需要做的工作就是安装 Nginx 和配置就好。Nginx 的安装就不说了,现在 Reposity 的源本身已经很安全,个人不必再迷信手动编译,因此我全部用的都是官网的包安装的,快速方便。PHP 我这里用的是 7.2版本。 以下就直接贴出配置文件。

php-fpm + php pool
# 查看 php-fpm 配置
# cat /etc/php/7.2/fpm/php-fpm.conf | grep -v "^;" | grep -v '^$'
[global]
pid = /run/php/php7.2-fpm.pid
error_log = /var/log/php7.2-fpm.log
include=/etc/php/7.2/fpm/pool.d/*.conf

# 查看 Pool 的配置
# cat /etc/php/7.2/fpm/pool.d/site-name.conf | grep -v "^;" | grep -v '^$'
[site-name]
user = www-data
group = www-data
listen = /run/php/site-name.sock
listen.owner = www-data
listen.group = www-data
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
request_terminate_timeout = 300s
request_slowlog_timeout = 10s
chdir = /var/www/html/site-name.com/public_html
slowlog = /var/www/html/site-name.com/logs/$pool.log.slow
php_flag[display_errors] = off
php_admin_value[display_errors]=0
php_admin_value[display_startup_errors]=0
php_admin_value[html_errors]=0
php_admin_value[define_syslog_variables]=0
php_admin_flag[file_uploads]=1
php_admin_flag[log_errors] = on
php_admin_value[log_errors]=1
php_admin_value[upload_tmp_dir]="/var/tmp"
php_admin_value[upload_max_filesize]="4M"
php_value[max_input_time]="120"
php_admin_value[max_input_time]=120
php_value[max_execution_time]="300"
php_admin_value[post_max_size]="4M"
php_value[session.gc_maxlifetime]=3600
php_admin_value[session.gc_probability]=1
php_admin_value[session.gc_divisor]=100
php_admin_value[error_log] = /var/log/fpm-php/site-name.log
php_admin_value[memory_limit] = 64M
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
php_admin_value[magic_quotes_gpc]=0
php_admin_value[register_globals]=0
php_admin_value[session.auto_start]=0
php_admin_value[mbstring.http_input]="pass"
php_admin_value[mbstring.http_output]="pass"
php_admin_value[mbstring.encoding_translation]=0
php_admin_value[expose_php]=0
php_admin_value[allow_url_fopen]=1
php_admin_value[safe_mode]=0
php_admin_value[cgi.fix_pathinfo]=1
php_admin_value[cgi.discard_path]=0

# Nginx 的配置分为 2 部分,Nginx全局环境 + Vhost 配置

# Nginx 全局配置

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
    worker_connections 768;
    # multi_accept on;
}
http {
    ##
    # Basic Settings
    ##
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # server_tokens off;
    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    ##
    # SSL Settings
    ##
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;
    ##
    # Logging Settings
    ##
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    ##
    # Gzip Settings - 这里是比较重要的地方
    ##
    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
  gzip_min_length 1100;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript font/truetypef font/opentype application/vnd.ms-fontobject text/x-component image/svg+xml;
 gzip_static on;
    ##
    # Virtual Host Configs
    ##
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

# Nginx - 针对 Drupal 站点的 vhost 配置
server {
    listen 80;
    server_name www.site-name.com site-name.com;
    add_header Strict-Transport-Security max-age=15768000;
    #永久重定向到 https 站点
    return 301 https://$server_name$request_uri;
    }

server {
    listen 443 ssl;
    server_name www.site-name.com site-name.com;
    root /var/www/html/site-name.com/public_html;
    keepalive_timeout   120;
    #证书文件
    ssl_certificate     /etc/certs/site-name.com/fullchain.pem;
    #私钥文件
    ssl_certificate_key /etc/certs/site-name.com/key.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    access_log /var/www/html/site-name.com/logs/access.log;
    error_log /var/www/html/site-name.com/logs/error.log;

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    # Very rarely should these ever be accessed outside of your lan
    location ~* \.(txt|log)$ {
        allow 192.168.0.0/16;
        deny all;
    }

    location ~ \..*/.*\.php$ {
        return 403;
    }

    location ~ ^/sites/.*/private/ {
        return 403;
    }

    # Block access to scripts in site files directory
    location ~ ^/sites/[^/]+/files/.*\.php$ {
        deny all;
    }

    # Allow "Well-Known URIs" as per RFC 5785
    location ~* ^/.well-known/ {
        allow all;
    }

    # Block access to "hidden" files and directories whose names begin with a
    # period. This includes directories used by version control systems such
    # as Subversion or Git to store control files.
    location ~ (^|/)\. {
        return 403;
    }

    location / {
        # try_files $uri @rewrite; # For Drupal <= 6
        try_files $uri /index.php?$query_string; # For Drupal >= 7
    }
    location @rewrite {
        rewrite ^/(.*)$ /index.php?q=$1;
    }
    # Don't allow direct access to PHP files in the vendor directory.
    location ~ /vendor/.*\.php$ {
        deny all;
        return 404;
    }
    location ~ '\.php$|^/update.php' {
        # Ensure the php file exists. Mitigates CVE-2019-11043
        try_files $uri =404;
        fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
        include fastcgi_params;
        # Block httpoxy attacks. See https://httpoxy.org/.
        fastcgi_param HTTP_PROXY "";
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param QUERY_STRING $query_string;
        fastcgi_intercept_errors on;
        fastcgi_pass unix:/run/php/site-name.sock;
    }
    location ~ ^/sites/.*/files/styles/ { # For Drupal >= 7
        try_files $uri @rewrite;
    }
    location ~ ^(/[a-z\-]+)?/system/files/ { # For Drupal >= 7
        try_files $uri /index.php?$query_string;
    }
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        try_files $uri @rewrite;
        expires max;
        log_not_found off;
    }
   # # Enforce clean URLs
    # Removes index.php from urls like www.example.com/index.php/my-page --> www.example.com/my-page
    # Could be done with 301 for permanent or other redirect codes.
    if ($request_uri ~* "^(.*/)index\.php(.*)") {
        return 307 $1$2;
    }
}

基于http 的 web 程序,由于其无状态的属性,很多时候并不是很适合循环定时执行的任务。Drupal 本身对于这种需求有多种解决方案,比如核心的 hook_cron, 第三方的现有模块 Utilimate Cron, Ellisa Cron, Scheduler Job等。然而核心 API 和现有模块的问题在于构建在在 drupal 的 cron 之上,本身核心的 cron 已经负担足够沉重,比较轻量的或者时间跨度比较长的任务比较适合通过这些途径来实现。当任务是长时间短间隔高频率循环执行的时候,通过这种方式很可能让核心的 cron 负担过于称重,一方面影响系统的稳定性,同时也会影响本身需求目标的实现。

以上解决办法主要是针对 shared host 用户,由于这一类用户没有完整的 Unix/Linux 主机权限,因此无法利用 Unix/Linux 核心自带的 crontab 带来的稳定性和遍历性。话说9102年都已经快结束了,这年头还没上云的用户或者还在用 sharedhost 跑 drupal 的用户不多了吧。

我要跑的任务是高频率定时循环执行的,所以以上的解决方案并不能很好的满足我的需求。

废话不多说,上解决办法。


解决方案1 - Crontab

思路如下

  1. 根据任务的需求定义路径
  2. 使用 crontab + curl 的方式请求路径,调用后台的 callback 执行相关的任务
  3. 这里需要注意的是需要做请求验证,验证其是否来自 127.0.0.1, token 是否正确,否则返回 403 错误

针对不同时段需要执行的任务,可以在 web 界面输出格式化的 crontab 列表,然后直接进入 shell 界面将这些任务加入 Linux 的 crontab 列表中即可

这里需要区分两种任务类型,一种是定时执行如每天12点钟运行,一种是循环执行如每隔10分钟执行一次。

比如说,每天的早上8点和晚上8点各运行一次更新任务,更新最新的产品信息和行业信息。

如何设计逻辑?(这里只针对目前手上已经有处理好的产品信息和行业信息的情况)

~~这里就需要用到 Drupal 核心的 Queue API + Utimate Cron API ~~
之前使用的是 Utimate Cron,这个模块的问题在于官方的文档实在是太烂了,使用他的最简设置是没问题的,比如我要使用数据库 logger 而非默认的缓存 logger, 该如何配置文档中没有说明。说是可以直接对接 drupal 的 queue, 我没有找到具体的设置方法。

考虑到快速上手解决问题,最终选取了 Queue API + Elysia Cron, 这里选用 Queue 的主要原因是当任务的负载很重且执行的时间很长,可能会超过PHP的 max_execution_time 时,使用队列的处理方式,每次执行一个任务或一小批任务,不至于进程卡死。

具体使用方式如下:

# 1.  使用  hook_cronapi() 定义定时任务,注意这里我的模块名是 jobs_every_minute
 /**
  * Implements hook_cronapi().
  */

  function jobs_every_minute_cronapi($op, $job = NULL){
    $items = array();
    $items['ec_job_test'] = array(
      'description' => 'Send nid to Queue',
      'rule' => '* * * * *',
      'arguments' => array(20),
      'callback' => 'jobs_every_minute_cron_callback',
    );
    return $items;
  }

# 2. 定义定时任务的队列
  /**
   * Implements hook_cron_queue_info().
   */
  function jobs_every_minute_cron_queue_info() {
    $queues = array();
    $queues['1st_test_queue'] = array(
      'worker callback' => 'jobs_every_minute_insert_sql_record',
      'time' => 2,
      'skip on cron' => TRUE,
    );
    return $queues;
  }

# 3. 定义定时任务执行时的回调

  function jobs_every_minute_cron_callback($arg){

    // 这里一定要用 get 的方法,而不是直接 new 一个 Queue 的对象
    // 因为 hook_cron_queue_info() 已经在模块载入的初始,声明了 Queue 的实例
    // 如果直接 new 的话,会直接覆盖我们声明好的 Queue,那样的话一堆函数回调就会断掉

    $queue = DrupalQueue::get('1st_test_queue');
    $queue->createQueue();
    for ($i=1; $i <= $arg; $i++){
      $queue->createItem($i);
      $item = $queue->claimItem(30);
    // 这里无需使用 deleteItem 方法来删除 item,因为 Queue 默认再 claimItem 之后会自动删除 item
    //$queue->deleteItem($item);
    }
  }

# 4. 定义队列中每一个 item 需要执行的操作,这里我是向数据库中插入一条记录并用 watchdog()打印一条日志来测试操作的情况

  function jobs_every_minute_insert_sql_record($item){

    db_insert('sql_insert_test')
      ->fields(array('text' => date("Y-m-d H:i:s",time())))
      ->execute();

    watchdog('Job scheduler' ,
      'Message: Data inserted to table at @t',
      array('@t'=> date("Y-m-d H:i:s",time())),
      WATCHDOG_NOTICE);
  }

这样一个完整 Queue + Cron API 的完整流程就走完了。

其实这里也是有问题的,这里我们定义的定时任务 ec_job_test 每运行一次都会在系统的 queue 表中插入新的 item,而要运行每一个 item 对应的处理任务,最终还是要运行系统的 cron。 即hook_cronapi+queue定义的定时任务,不是即时执行的,需要drupal核心的 cron 运行时,检查 queue 表中的 item, 如果有任务,就会执行。弄了半天根本没有解决我的问题。我的需求是定时添加新的处理任务,并实时监听任务状态,如果有则立即执行。由于核心的 cron 负载很重,我不可能每分钟运行一下,因此这个解决办法肯定是不行的。

回到最开始的思路,既然我能使用 linux 的 crontab,使用这些第三方的 cron 模块真的是多此一举。

我只需要定义一个路径并使用 curl + crontab 定时请求来扫描任务状态,如果有任务,每次返回一条,并使用回调处理,如果没有则闲置, 简单方便。


解决方案2 - PHP 添加进程控制扩展

MAMP 安装php第三方扩展库 - 进程控制扩展 Ev

本来这个问题在linux上极好解决,直接 pear 命令安装即可。然而在 OSX 上面却很痛苦,是的,因为我们用的都是 MAMP 的集成环境。

那么怎么办?

比如我现在用的 PHP 版本为 php7.0.15
那么这个版本 PHP 对应的 pear 的包管理工路径为
/Applications/MAMP/bin/php/php7.0.15/bin/pear

PHP 插件的预编译工具的路径为
/Applications/MAMP/bin/php/php7.0.15/bin/phpize

我没有使用 pear 命令的方式安装,因为感觉可能会报错安装失败,所以最终还是选择手动编译安装。

# 1. 下载 Ev 扩展的源码包
# 下载地址为 https://pecl.php.net/get/ev-1.0.6.tgz  wget 或是 curl 就看大家的喜好了
# 2. 解压 tgz 压缩包
tar -xzvf ev-1.0.6.tgz
# 3. 进入源码目录,注意源码目录直接就能看到 php5 和 php7 文件夹,不是含有 xml 文件的那个目录
cd ev-1.0.6/ev-1.0.6
# 4. 运行预编译工具,获取系统变量
/Applications/MAMP/bin/php/php7.0.15/bin/phpize
# 5. 编译前配置
./configure --with-ev
# 6. 解决依赖错误问题
# error dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib
# 出现这个问题的原因是插件依赖的readline库文件太老了, OSX 系统已经由 libreadline.8.0.dylib 取代了老版本
# 创建一个软连接,解决依赖的问题
cd /usr/local/opt/readline/lib
ln -s libreadline.8.0.dylib libreadline.7.dylib
# 切换回原来的安装目录
cd -
# 7. 重新配置
./configure --with-ev --with-php-config=/Applications/MAMP/bin/php/php7.0.15/bin/php-config
# 配置运行结果出现一下表示通过
> creating libtool
> appending configuration tag "CXX" to libtool
> configure: creating ./config.status
> config.status: creating config.h
# 8. 编译并且安装
make && make install
# 提示编译成功别切扩展安装至相关目录 
> Build complete.
> Don't forget to run 'make test'.
> 
> Installing shared extensions: /Applications/MAMP/bin/php/php7.0.15/lib/php/extensions/no-debug-non-zts-20151012/
# 如果你出现了 ev-1.0.6/ev-1.0.6/php7/common.h:25:10: fatal error: 'php.h' file not found 这样的错误,是因为编译器没有找到相关的 php环境 配置器的地址,需要重新配置并带上参数 --with-php-config=php-config (对应 php 版本的配置器地址)
# 查看编译后的插件
ls /Applications/MAMP/bin/php/php7.0.15/lib/php/extensions/no-debug-non-zts-20151012/
# 发现 ev.so 躺在那里,编译的工作算是完成了。
# 9. 启用扩展
# 打开 MAMP,菜单 -> File -> Edit Template -> PHP(php.ini) -> 7.0.15
# 找到第 538 行,追加如下设置项
extension=ev.so
# 保存后重启 MAMP 即可

解决方案3 - PHP DAEMON 方案 - 守护进程

此方案需要用到的模块为 drushd + PHP-DAEMON 扩展库

写在前

这个世界上解决问题的方法论有很多,然而我认为最有效的还是工程化。

Wikipedia 针对工程化 engineering 的定义如下:
The creative application of scientific principles to design or develop structures, machines, apparatus, or manufacturing processes, or works utilizing them singly or in combination; or to construct or operate the same with full cognizance of their design; or to forecast their behavior under specific operating conditions; all as respects an intended function, economics of operation and safety to life and property.

翻译过来的意思如下:

创造性的应用自然科学的原理设计和开发的服务在经济运行、生命财产安全领域有特定用途的实体或者设计,包括单一的或者组合式的结构、机器、成套设备、生产流程,构建和运行以上设备和流程的设计方案,根据运行状况预测行为模式的模型等的活动统称为工程化。

工程化除了跟自然科学绑定在一起外,更重要的一个概念是成本优化。实现功能或目标的效益要大于付出的成本,这样才能有正收益,工程化才有意义。

如果一件事情机器可以很好的完成任务,则没有必要去使用人工,人工在很多时候都存在单位成本高,效率低下,容易犯错的问题。比如针对 Made In China 产品更新与发布这件事上,人工完全可以被机器取代。

MIC批量发布的工程化方案

  • 产品元数据 (主要对应MIC里面的字段)

    • 基本信息
      • 产品名称 (标题 = 前缀 + 形容词 + 产品关键词 + 后缀)
      • 产品型号
      • 产品关键词
      • 产品图片
    • 产品属性
      • 产品通用属性
      • 产品专用属性
    • 贸易信息 (固定内容)
    • 产品详情
      • 标题
      • 公司简介 (提示相关性)
      • 公司优势 (关怀强化)
      • 产品应用 (连接问题)
      • 详细介绍 (相关性强化 - 解决问题)
      • 包装运输 (增强信任)
      • 生产设备 (增强信任)
      • 质量管控 (增强信任)
      • 资质认证 (增强信任)
      • 联系方式 (转化阶段)
  • 产品发布流程的工程化


MIC是一个基于 web 的 Application, 要完成产品发布,操作需要在浏览器上来完成。产品发布的主要操作为表单的提交( POST方式 )。针对这个操作有很多现成的解决办法,比如 terminal 下的 curl, firefox 的 micros, windows下的火车采集(火车浏览器),Python 自动化测试技术 Selenium,爬虫技术(Scrapy / Scrapy + Splash)。

MIC的登录表单是通过JS的方式提交登录信息,且产品信息填充页面的很多地方都是使用 JS 来验证数据有效性,我是个 JS 菜鸡,因此最终选择了 Selenium 这个解决方案。好处是,我不用考虑 JS 加密与解密的问题,坏处是这种方式操作的效率不高,不过考虑到产品发布这种可以24小时运行对执行时间不敏感的需求来说,Seleminum 够用了,向来我的追求就是能跑就行,还要啥自行车呢。

预警 - Selenium 是需要写代码的,厌恶代码的同学可以绕过了。Talk is cheap,show me the code.


项目的主要结构如下

MIC
+-- data
| + -- products_data.xlsx (产品元数据/基础数据)
+-- images (产品主图)
| + -- images1.jpg
| + -- images2.jpg
| + ...
+-- excel.py ( 将 excel 表格中的数据导入到数据库中,让数据持久化、状态化以方便后期知道任务进行到了哪一步 )
+-- product_node.py ( MIC产品类 - 用于生成需要发布的产品的详细描述 body , 即 WYSJWYG 编辑器需要填充的内容)
+-- mic_publish.py ( 产品发布的操作 - 包括登录账号、设置产品内容、上传图片、添加详细描述、提交发布等 )


以下只针对 product_node.py 和 mic_publish.py 这两处代码进行展示。

产品发布的操作


from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from retry import retry
from product_node import ProductNode
import pymysql
import random
import time

class MicProductPublish:
    #  账户登录信息
    LOGIN_URL = 'https://login.made-in-china.com/sign-in/'
    USER_NAME = 'mic_account_name'
    PASSWORD = 'mic_password'
    # 单个产品的镜像发布数量
    PRODUCT_NODE_MIRRORS = 7
    # 产品模板的链接
    PRODUCT_TEMPLATE_PAGE = 'https://membercenter.made-in-china.com/productmanage.do?xcase=addSimilar&prodId=similar_products_id'
    # 每个产品上传的图片数量
    UPLOAD_IMG_NUMS = 4
    # 可上传的总的图片数量
    PRODUCT_IMG_NUMS = 65
    UNIX_SOCKET = "/Applications/MAMP/tmp/mysql/mysql.sock"
    # 自定义参数属性
    GRADE_SELECT_DICT = {
        '1000': 'FQfmnGaYDEHo',
        '2000': 'CmtxEBGAjnib',
        '3000': 'ynAxmZaDuQHe',
        '4000': 'smfJQhtCuniq',
        '5000': 'CmtEQNGxjJib',
        '6000': 'ZJAnxQfuDEIo',
        '7000': 'sJaQEetSunIo',
        'other': '-1'
    }

    def __init__(self, task_counts):

        self.browser = webdriver.Firefox()
        self.login()

        # 连接数据库
        self.db = pymysql.connect(user='database_user',
                                  password='database_password',
                                  database='database_name',
                                  cursorclass=pymysql.cursors.DictCursor,
                                  unix_socket=self.UNIX_SOCKET)
        # 单次执行发布认为的牌号数量
        self.task_counts = task_counts

    def get_publish_schedule(self):
        # 第一批只操作常见牌号(未发布),获取目标牌号,并限定数量为task_counts
        sql = '''
            SELECT `Grade` FROM `database_name`.`grade` WHERE `Common`=1 AND `Published`=0 LIMIT {task_counts};
        '''.format(task_counts=self.task_counts)

        cursor = self.db.cursor()
        cursor.execute(sql)
        # 返回列表结构 待发布的产品牌号信息,数据结构为{'Grade':'1050'}
        return cursor.fetchall()

    def run_publish_schedule(self):

        # 执行产品发布任务
        # 并对后台的数据库中的状态进行修改
        #
        # 1. 执行单个产品发布任务
        # 2. 修改数据库中此牌号的状态

        for g in self.get_publish_schedule():

            m = self.PRODUCT_NODE_MIRRORS
            # 针对一个牌号发布指定数量的镜像产品
            # 添加 任务出错时,再次尝试, 使用retry包
            while m:
                self.go_to_template_page()
                self.publish_keyword_product_node(g['Grade'])
                m -= 1
            self.update_database(g['Grade'])

    @retry(tries=3, delay=2, max_delay=5)
    def publish_keyword_product_node(self, grade_str):
        self.go_to_template_page()
        self.publish_keyword_product_node_fill_content_and_submit(grade_str)

    def publish_keyword_product_node_fill_content_and_submit(self, grade_str):

        # 这里主要是针对浏览器的操作
        # 关于产品信息的构建,使用 产品详情(PD)类

        # 1. 创建产品类的新实例(牌号)
        product_node = ProductNode(grade_str, self.db)
        # 2.填写产品基本信息
        self.fill_base_info(product_node)
        # 3.上传产品图片
        self.upload_images()
        # 4.填写产品属性
        # - 4.1 颜色/应用/认证/工艺 (这里模板已经确定,不用操作)
        # - 4.2 选择牌号信息
        # - 4.3 回火/表面处理/是否合金/包装运输/规格/商标/原产地/Finish/内包装 (这里模板已经确定,不用操作)
        self.input_properties(product_node)
        # 5.贸易信息(这里模板已经确定,不用操作)
        # 6. 产品详情
        self.fill_body(product_node)
        # 7. 提交发布
        self.browser.implicitly_wait(2)
        self.publish_submit()
        self.browser.implicitly_wait(2)

    # 登录MIC
    def login(self):
        self.browser.get(self.LOGIN_URL)
        user = self.browser.find_element_by_id('logonInfo.logUserName')
        user.send_keys(self.USER_NAME)
        passwd = self.browser.find_element_by_id('logonInfo.logPassword')
        passwd.send_keys(self.PASSWORD)
        submit = self.browser.find_element_by_id('sign-in-submit')
        submit.click()

    # 回到默认模板页面
    def go_to_template_page(self):
        self.browser.get(self.PRODUCT_TEMPLATE_PAGE)
        # 隐式等待,检测页面的载入状态,如完全载入则立即结束等待
        self.browser.implicitly_wait(3)

    def fill_base_info(self, product_node):

        # 填写产品标题
        WebDriverWait(self.browser,20).until(EC.visibility_of_all_elements_located((By.CSS_SELECTOR,'textarea.name-enterarea')))
        product_name = self.browser.find_elements_by_css_selector('textarea.name-enterarea')
        product_name[0].send_keys(product_node.build_product_title())

        # 填写产品型号
        product_model = self.browser.find_element_by_name('prodModel')
        product_model.click()
        product_model.send_keys(product_node.get_keyword_grade()['Grade'] + ' keyword')
        self.browser.implicitly_wait(1)

        # 选择中心词
        self.set_center_word()

        # 填写产品关键词信息
        keywords = product_node.build_product_keyword()
        for k in keywords:
            keyword = self.browser.find_element_by_id('prodKeyword' + str(keywords.index(k)))
            keyword.clear()
            keyword.send_keys(k)

        self.browser.implicitly_wait(1)

    def set_center_word(self):

        WebDriverWait(self.browser,20).until(EC.visibility_of_all_elements_located((By.CSS_SELECTOR,'.J-center-words-box')))
        center_words = self.browser.find_elements_by_css_selector('.J-center-words-box .input-checkbox')
        keyword_center_word = self.browser.find_elements_by_css_selector('.J-center-words-box .input-checkbox[value="keyword"]')

        if keyword_center_word:
            keyword_center_word[0].click()keyword
        else:
            center_words[0].click()

    def upload_images(self):

        # 产品图片板块
        imgs = self.build_images_path()
        css = 'input.upload-selector.J-uploader'
        # 上传图片
        for i in imgs:
            upload = self.browser.find_element_by_css_selector(css)
            self.browser.implicitly_wait(3)
            upload.send_keys(i)

    def build_images_path(self):

        imgs = []

        files_list = list(range(1,self.PRODUCT_IMG_NUMS+1,1))

        for i in random.sample(files_list, self.UPLOAD_IMG_NUMS):
            imgs.append('/MIC/images/image' + str(i) + '.jpg')
        return imgs

    def input_properties(self, product_node):

        # 产品属性板块
        form_select = self.browser.find_elements_by_css_selector('.J-prop-select')

        # 设置牌号
        # 可选值
        grade_select = Select(form_select[2])
        self.set_product_property_grade(product_node.get_keyword_grade()['Series'], grade_select)

        # 设置产品状态
self.browser.find_element_by_id('JindustryPropOtherValue6').send_keys('Mill finish or as your request')

    def fill_body(self, product_node):
        # 切换到编辑器的iframe
        wysiwyg = self.browser.find_element_by_css_selector('.cke_wysiwyg_frame')
        self.browser.switch_to.frame(wysiwyg)
        # 获取主编辑界面
        editor_body = self.browser.find_element_by_css_selector('.cke_editable')
        editor_body.clear()
        # 获取产品信息
        self.browser.switch_to.default_content()
        # 设置产品详细描述
        product_body = product_node.build_product_body()
        # 填充产品详情
        self.browser.execute_script(product_body)
        self.browser.implicitly_wait(2)

        # 切换回页面的主要内容
    def publish_submit(self):

        WebDriverWait(self.browser,10).until(EC.element_to_be_clickable((By.CSS_SELECTOR,'.J-product-submit')))
        btn = self.browser.find_element_by_css_selector('.J-product-submit')
        btn.click()

    def update_database(self, grade_str):

        time_stamp = str(int(time.time()))

        update_sql = 'UPDATE `database_name`.`grade` SET `Published` = 1,  `Published_Date` = "{0}" WHERE `GRADE` = "{1}";'.format(time_stamp, grade_str)
        cursor = self.db.cursor()
        cursor.execute(update_sql)
        # 同步修改到数据库
        self.db.commit()

    def __del__(self):
        self.db.close()
        self.browser.close()

if __name__ == '__main__':
    # 发布20个产品,可以设置更大的值
    MicProductPublish(20).run_publish_schedule()

生成单个产品内容

# 生成产品详情的类
import pymysql
import time
import random

class ProductNode:
    # 类公共变量 - 产品主要详情 - 含有主要字段的placetoken
    PRODUCT_MAIN_BODY = '<br><h2>{product_title}</h2><br><p style=\"color:#666666;text-align:justify;font-size:14px;font-family:Arial, Helvetica, sans-serif;\"><br><img title=\"Company profile of {product_title}\" alt=\" Company profile of {product_title}\"     src=\"//image.made-in-china.com/226f3j00WuqQRGJcaZzh/New-Product-Test-2-for-Image-Name.webp\" srcid=\"275914412\" width=\"1060px\"><br> </p><p style=\"color:#666666;font-size:14px;font-family:Arial, Helvetica, sans-serif;\"><span style=\"font-size:20px;\">Technology driven quality focused keyword & keyword alloy manufacturer</span><br> </p><ul><li><p><span style=\"font-size:16px;\">Henan  keyword Industry Group was founded in 2006, located in Gongyi industry area of Henan Province.</span></p></li><li><p><span style=\"font-size:16px;\">The capacity of our keyword Rolling products has reached 200,000 ton per year, formats available in coil, plates, sheets and foil, material of keyword alloy production line covers 1000-8000 series.</span></p></li><li><p><span style=\"font-size:16px;\">Our stable consistent supply chain, outstanding quality control system and first-rate services have been approved and highly praised by our clients, and built us a good reputation among this industry.</span></p></li><li><p><span style=\"font-size:16px;\">Category of our customers is full of diversity and fall into the zone of transportation, architecture, engineering, aviation, space, electricity and package where our keyword products have been widely used.</span><br><br><br> </p></li></ul><img title=\" company advantages of {product_title}\" alt=\" company advantages of {product_title}\" src=\"//image.made-in-china.com/226f3j00lrkaRudBgKgW/New-Product-Test-2-for-Image-Name.webp\" srcid=\"275914422\" width=\"1060px\"><br><br><br><img title=\"Product Application of {product_title}\" alt=\"Product Application of {product_title}\" src=\"//image.made-in-china.com/226f3j00WucERIAjhKpl/New-Product-Test-2-for-Image-Name.webp\" srcid=\"275914432\" width=\"1060px\"><h3 style=\"color:#0078cc;font-size:20px;font-family:Arial, Helvetica, sans-serif;\">Application</h3><br><div class=\"rich-text-table\"><table style=\"width:1037px;\" cellspacing=\"1\" cellpadding=\"1\" border=\"1\"><tbody><tr><td colspan=\"3\" style=\"text-align:center;width:1028px;\"><br><span style=\"font-size:20px;\">Application of {product_title}</span><br> </td></tr><tr><td style=\"width:292px;\"><ul><li><p><span style=\"font-size:16px;\">Transportation</span></p><ul><li><span style=\"font-size:16px;\">Aircraft & Aerospace Automobile</span></li><li><span style=\"font-size:16px;\">Bus</span></li><li><span style=\"font-size:16px;\">Truck</span></li><li><span style=\"font-size:16px;\">Tank Train</span></li><li><span style=\"font-size:16px;\">Metro Line</span></li><li><span style=\"font-size:16px;\">Ship</span></li><li><span style=\"font-size:16px;\">Boat</span></li><li><span style=\"font-size:16px;\">Yacht</span></li></ul></li></ul></td><td style=\"width:367px;\"><ul><li><p><span style=\"font-size:16px;\">Building & Construction</span></p><ul><li><span style=\"font-size:16px;\">Curtain Wall</span></li><li><span style=\"font-size:16px;\">Roofing Decoration</span></li><li><span style=\"font-size:16px;\">Celling Door</span></li><li><span style=\"font-size:16px;\">Window</span></li><li><span style=\"font-size:16px;\">Floor Framework</span></li><li><span style=\"font-size:16px;\">Structure</span></li></ul></li></ul></td><td style=\"width:354px;\"><ul><li><p><span style=\"font-size:16px;\">Packaging & Container</span></p><ul><li><span style=\"font-size:16px;\">Can</span></li><li><span style=\"font-size:16px;\">Box</span></li><li><span style=\"font-size:16px;\">Case</span></li><li><span style=\"font-size:16px;\">Container Seal</span></li><li><span style=\"font-size:16px;\">Lid Cover</span></li><li><span style=\"font-size:16px;\">Flexible Package</span></li><li><span style=\"font-size:16px;\">Tube Foil</span></li><li><span style=\"font-size:16px;\">Household Foil</span></li></ul></li></ul></td></tr><tr><td style=\"width:292px;\"><ul><li><p><span style=\"font-size:16px;\">Electronics & Appliances</span></p><ul><li><span style=\"font-size:16px;\">Computer</span></li><li><span style=\"font-size:16px;\">Laptop</span></li><li><span style=\"font-size:16px;\">Communication Tools</span></li><li><span style=\"font-size:16px;\">Consumer Electric</span></li><li><span style=\"font-size:16px;\">Heat Exchanger</span></li></ul></li></ul></td><td style=\"width:367px;\"><ul><li><p><span style=\"font-size:16px;\">Machine & Equipment</span></p><ul><li><span style=\"font-size:16px;\">Catering Equipment</span></li><li><span style=\"font-size:16px;\">Textile Machinery</span></li><li><span style=\"font-size:16px;\">Precision Instrument</span></li><li><span style=\"font-size:16px;\">Medical Equipment</span></li></ul></li></ul></td><td style=\"width:354px;\"><ul><li><p><span style=\"font-size:16px;\">Durable Goods</span></p><ul><li><span style=\"font-size:16px;\">Cookware</span></li><li><span style=\"font-size:16px;\">Kitchen Utensils</span></li><li><span style=\"font-size:16px;\">Lamp Cover</span></li><li><span style=\"font-size:16px;\">Air Outlet</span></li><li><span style=\"font-size:16px;\">Light Reflecting Plate</span></li><li><span style=\"font-size:16px;\">Traffic Sign</span></li><li><span style=\"font-size:16px;\">Nameplate</span></li></ul></li></ul></td></tr></tbody></table></div><br><br><br> {keyword_app_info} <h3 style=\"color:#0078cc;font-size:20px;font-family:Arial, Helvetica, sans-serif;\">Element Components</h3><br><br><br><p>Element Components of {product_title}</p><br><div class=\"rich-text-table\">     {element_table} </div><br><br><br><h3 style=\"color:#0078cc;font-size:20px;font-family:Arial, Helvetica, sans-serif;\">Temper</h3><br><br><br><p>Temper of {product_title}</p><br><div class=\"rich-text-table\">     {temper_table} </div><br><br><br><h3 style=\"color:#0078cc;font-size:20px;font-family:Arial, Helvetica, sans-serif;\">Forms</h3><br><br><br><div class=\"rich-text-table\"><p>Common forms of {product_title}</p><br><br>     {forms_list} </div><br><br><br><p>Dimension, tolerance and packing requirement of {product_title} upon request. Please check with our sales to get more information.</p><br><br><br><img title=\"Product package of {product_title}\" alt=\"Product package of {product_title}\" src=\"//image.made-in-china.com/226f3j00SpyQDFtggjzI/New-Product-Test-2-for-Image-Name.webp\" srcid=\"278033782\" width=\"1060px\"><br><br><img title=\"Equipments and Facilities of {product_title}\" alt=\"Equipments and Facilities of {product_title}\" src=\"http://image.made-in-china.com/44f3j00NrPaUWDLaMpn/Product-New-Template-2-Sheet-Plate.jpg\" srcid=\"275914452\" width=\"1060px\"><ul><li><p><span style=\"font-size:16px;\"> keyword Industry has one of one of the most diverse choices of keyword handling equipment in the nation.</span></p></li><li><p><span style=\"font-size:16px;\">We add brand-new equipment to our centers regularly, always striving to use the most recent technology in order to fulfill our the altering requirements of customer.</span></p></li><li><p><span style=\"font-size:16px;\">Our capacity to accomplish high customer complete satisfaction and on-time shipment is largely due to our specialized and educated maintenance personnel and maker drivers.</span></p></li><li><p><span style=\"font-size:16px;\">Our upkeep employees keep our handling, material handling and also delivery tools operating and also offered all the time.</span><br><br> </p></li></ul><br><br><img title=\" Quality Control of {product_title}\" alt=\" Quality Control of {product_title}\" src=\"//image.made-in-china.com/226f3j00VucEUMdaaKpi/New-Product-Test-2-for-Image-Name.webp\" srcid=\"275914462\" width=\"1060px\"><ul><li><p><span style=\"font-size:16px;\"> keyword Industrial is devoted to quality management by providing keyword products and associated services that meet or go beyond client expectations in a sustainable manner while constantly keeping track of and enhancing our products and processes.</span></p></li><li><p><span style=\"font-size:16px;\">Likewise we preserves a high quality of keyword products throughout the entire production process. From the delivery of ingot and smelting to the final dimensional check, substantial attention is provided to all products as outlined by process control checklist for each casting requirement.</span></p></li><li><p><span style=\"font-size:16px;\">We have our own state-art lab and devoted product establishing and evaluation engineers team, a full set test devices including Tensile Tester, Surface Roughness Testers, Direct Reading Emission Spectrometer, Ultrasonic Flaw-detecting Machine, Hardness Testers, Metallurgical Microscope etc.</span></p></li><li><p><span style=\"font-size:16px;\">Our Continuously Hot-rolled keyword Coil Line and Tandem Cold Mills are both import from SMS Group German, the Digital Manufacturing Dispatch Center of our company could fetch real-time operation data feeds directly from the terminal of our production line, which makes us able to locate the problem and do the correction at the very first time. The Quality Assurance system has actually been deeply integrate into every action of our keyword processing flow besides. This is the main reason that we could support our partners from different markets with competitive cost and outstanding quality.</span><br><br> </p></li></ul><img title=\" Certificates of{product_title}\" alt=\" Certificates of {product_title}\" src=\"//image.made-in-china.com/226f3j00iuoQYZJGgjzW/New-Product-Test-2-for-Image-Name.webp\" srcid=\"275914472\" width=\"1060px\"><br><br><br><span style=\"font-size:16px;\">Currently for keyword and keyword alloy sheets and coil, we have passed ISO 9001-2007 Quality Control System Verification. The Certificates displayed here are China Classification Society certificate, DNV Approval of Manufacturer Certificate, CE Certificate for Europe, and SGS Inspection Report.</span><br><br><br><br><br><br><img title=\"Contact sales of {product_title}\" alt=\"Contact sales of {product_title}\" src=\"//image.made-in-china.com/226f3j00hubaYFwzhjri/New-Product-Test-2-for-Image-Name.webp\" srcid=\"275914482\" width=\"1060px\"><br><br><br><br>'

    TITLE_PREFIX = [
        'Best Quality ',
        'Hot Sale ',
        'ASTM JIS EN Standard ',
        'ISO Certificated ',
        'Bottom Price ',
        'Bright Finish ',
        'Rolled ',
        'Top Rated ',
        'Low Price ',
        ]

    TITLE_SUFFIX = [
        ' From Factory',
        ' From Qualified Supplier',
        ' From Audited manufacturer',
        ' Full Size Available',
        ' Fresh Stock',
        ' Factory Direct Sale',
        ' Price Per Ton',
        ' Best Offer Guarantee',
        ]

    PRODUCT_TITLE = ''

    keyword_TEMPERS = {
        '1000': ['F', 'O', 'H'],
        '2000': ['F', 'O', 'W', 'T'],
        '3000': ['F', 'O', 'H'],
        '4000': ['F', 'O', 'W','T'],
        '5000': ['F', 'O', 'H'],
        '6000': ['F', 'O', 'W','T'],
        '7000': ['F', 'O', 'W','T'],
        '8000': ['F', 'O', 'H']
    }

    keyword_TEMPERS_CODES = {
        'F': {
            'defination': 'As fabricated. No special control has been performed to the heat treatment or strain hardening after the shaping process such as casting, hot working, or cold working.',
            'codes': ['F']
        },
        'O': {
            'defination': 'Annealed - This is the lowest strength, highest ductility temper.',
            'codes': ['O'],
        },
        'H': {
            'defination': 'Strain Hardened - (applied to wrought products only) Used for products that have been strengthened by strain hardening, with or without subsequent heat treatment. The designation is followed by two or more numbers as discussed below.',
            'codes': ['H12',
                      'H14',
                      'H16',
                      'H18',
                      'H19',
                      'H111',
                      'H112',
                      'H116',
                      'H21',
                      'H22',
                      'H24',
                      'H26',
                      'H28',
                      'H32',
                      'H321',
                      'H34',
                      'H36',
                      'H38']
        },
        'W': {
            'defination': 'Solution Heat Treated - This is seldom encountered because it is an unstable temper that applies only to alloys that spontaneously age at ambient temperature after heat treatment.',
            'codes': ['W']
        },
        'T': {
            'defination': 'Solution Heat Treated - Used for products that have been strengthened by heat treatment, with or without subsequent strain hardening. The designation is followed by one or more numbers as discussed below.',
            'codes': [
                'T0',
                'T1',
                'T2',
                'T3',
                'T4',
                'T42',
                'T5',
                'T6',
                'T651',
                'T7',
                'T8',
                'T9',
                'T10']
        }
    }

    # 构造函数,类实例化的时候会自动调用该函数
    def __init__(self, keyword_grade, db):
        # 初始化数据库连接
        self.db = db
        self.keyword_grade = keyword_grade
        self.PRODUCT_TITLE = self.build_product_title()

    # 获取产品牌号信息,指针对常用牌号
    def get_keyword__grade(self):
        if self.keyword_grade:
            grade_query_sql = 'SELECT * FROM `database_name`.`grade` WHERE `Grade` = "{0}";'.format(self.keyword_grade)
            cursor = self.db.cursor()
            cursor.execute(grade_query_sql.format(self.keyword_grade))
            return cursor.fetchone()
        else:
            return None

    def get_keyword_temper(self):
        if self.keyword_grade:
            grade = self.get_keyword_grade()
            keyword_temper = self.keyword_TEMPERS[str(grade['Series'])]
            return keyword_temper
        else:
            return False

    # 获取产品特定牌号的应用信息
    def get_keyword_app(self):
        app_query_sql = 'SELECT * FROM `database_name`.`application` WHERE `Grade` = "{0}";'.format(self.keyword_grade)
        cursor = self.db.cursor()
        cursor.execute(app_query_sql.format(self.keyword_grade))
        result = cursor.fetchone()
        if result['App_zh'] == 'nan':
            result['App_zh'] = None
        if result['App_en'] == 'nan':
            result['App_en'] = None
        return result

    def build_keyword_app_info(self):
        app_dict = self.get_keyword_app()

        app_info = '<br> <p>Typical application of {product_title}</p> <br> <p>{app_en}</p> <br> <br> <br>'

        if app_dict['App_en']:
            return app_info.format(product_title=self.PRODUCT_TITLE, app_en = app_dict['App_en'])
        else:
            return ''

    # 获取产品牌号的化学成分信息
    def get_keyword_component(self):
        grade_query_sql = 'SELECT * FROM `database_name`.`component` WHERE `Grade` = "{0}";'.format(self.keyword_grade)
        cursor = self.db.cursor()
        cursor.execute(grade_query_sql)
        return cursor.fetchone()

    def get_keyword_form(self):
        forms = self.keyword_FORMS[str(self.get_keyword_grade()['Series'])]
        return forms

    def build_product_keyword(self):
        keywords = []
        forms = self.get_keyword_form()
        if len(forms) <=3:
            for f in forms:
                keywords.append(' '.join([self.keyword_grade, 'keyword', f]))
        else:
            three_forms = random.sample(forms, 3)
            for g in three_forms:
                keywords.append(' '.join([self.keyword_grade, 'keyword', g]))

        return keywords

    def build_product_title(self):
        prefix = ''.join(random.sample(self.TITLE_PREFIX, 1))
        suffix = ''.join(random.sample(self.TITLE_SUFFIX, 1))
        alloy_str = ''

        if 'A' not in self.keyword_grade:
            alloy_str = ''.join(random.sample(['', 'A', 'AA'], 1))

        forms_str = alloy_str + self.keyword_grade + ' keyword ' + '/'.join(self.get_keyword_form())
        return prefix + forms_str + suffix

    def build_comp_form(self):
        tr_list = self.build_table_tr_list()

        # 构建表格的头部区域
        table = '<table style="width:500px;" cellspacing="1" cellpadding="1" border="1"><thead><tr><td style="width:120px; text-align: center;">' + tr_list[0]['ele'] + '</td><td style="text-align: center;">' + tr_list[0]['value'] +'</td></tr></thead><tbody>'
        for i, tr in enumerate(tr_list):
            if i >= 1:
                if tr['rowspan']:
                    table += '<tr><td style="width:120px; text-align: center">' + tr['ele'] + '</td><td rowspan="2" style="text-align: center;">' + tr['value'] + '</td></tr>'
                elif tr['td'] == '':
                    table += '<tr><td style="width:120px; text-align: center">' + tr['ele'] + '</td></tr>'
                else:
                    table += '<tr><td style="width:120px; text-align: center">' + tr['ele'] + '</td><td style="text-align: center;">' + tr['value'] + '</td></tr>'

        table += '</tbody></table>'

        return table

    def build_table_tr_list(self):
        comp = self.get_keyword_component()

        table_tr_list = []

        for key, value in comp.items():
            if key == 'id':
                pass
            else:
                table_tr_list.append({'ele': key, 'value': value, 'rowspan': False, 'td': 'Default'})

        for i, tr in enumerate(table_tr_list):

            # rowspan 键指示了需要colspan的ta单元格
            # td值为''且不为'Default'时指示了 化学元素的单元格不生成

            if tr['value'] == 'nan':
                table_tr_list[i-1]['rowspan'] = True
                table_tr_list[i]['td'] = ''

        return table_tr_list

    def build_temper_table(self, keyword_temper):
        if isinstance(keyword_temper, list):

            table_body = '<table style="width:1037px;" cellspacing="1" cellpadding="1" border="1"><tbody>'

            for i in keyword_temper:
                table_body += '<tr><td style="width:120px; text-align:center;">' + i + '</td>'
                table_body += '<td><p style="font-size: 12px; font-style:italic; padding: 10px;">' + self.keyword_TEMPERS_CODES[i]['defination'] + '</p>'
                temper_codes_string = ' / '.join(self.keyword_TEMPERS_CODES[i]['codes'])
                table_body += '<p style="font-size: 12px; font-style: italic; padding: 10px;">' + temper_codes_string + '</p></tr>'

            table = table_body + '<tbody></table>'
            return table
        else:
            return ''

    def build_keyword_forms(self):
        forms = self.get_keyword_form()
        forms_output = '<ul>'
        for f in forms:
            forms_output += '<li><p><span style="font-size:16px;">' + f + '</p></li>'
        forms_output += '</ul>'
        return forms_output

    def build_product_body(self):
        return self.PRODUCT_MAIN_BODY.format(
            product_title=self.PRODUCT_TITLE,
            keyword_app_info=self.build_keyword_app_info(),
            element_table = self.build_comp_form(),
            temper_table=self.build_temper_table(self.get_keyword_temper()),
            forms_list=self.build_keyword_forms(),
        )

    def __del__(self):
        pass

跑任务很简单,只需要切换到命令行页面,输入

python3 mic_publish.py

然后浏览器就会自动的为你发产品了。

实测每 2 秒可以发布一个产品,一个小时可以稳定发布 1000+ 产品,偶尔会失败,失败后重试基本都会通过。

LNMP安装与配置

前期安装

  1. 更新 source list

# 这里我主要用的是linux - Debian的发行版,因此Reposity都是debian官方的。
# 尽量不要使用VPS服务商提供的Reposity,虽然更新可以省流量,
# 但是有时候有的软件包可能被修改,或是功能被阉割,部署到生产服务器有时候会出现莫名其妙的问题
# 因此还是建议使用官方的Reposity

###### Debian Main Repos ######
deb http://ftp.us.debian.org/debian/ jessie main contrib non-free
deb-src http://ftp.us.debian.org/debian/ jessie main contrib non-free

###### Debian Security Repos ######
deb http://security.debian.org/ jessie/updates main contrib non-free
deb-src http://security.debian.org/ jessie/updates main contrib non-free

###### Debian Backports Repos ######
deb http://ftp.debian.org/debian jessie-backports main
  1. 安装mosh
  2. 安装git, fail2ban
  3. 安装curl
  4. 设置系统时间与字符编码UTF-8

@TODO 以上几个重要的软件包有时间再写

LAMP

1. 安装apache2

apt-get install apache2 
#由于我们需要使用clean url,因此需要激活apache的rewrite模块
a2enmod rewrite
  1. 安装mariadb - mysql
apt-get install mariadb-server mariadb-client
# 运行mysql_secure_installation 提升数据库初始化的安装性
mysql_secure_installation

apt-get install mariadb-server mariadb-client

#运行mysql_secure_installation 提升数据库初始化的安全性
mysql_secure_installation

#建立mysql用户
create user 'user'@'localhost' identified by 'passwd';

#配置数据库用户权限
GRANT ALL PRIVILEGES ON dbname.* TO 'user'@'localhost' WITH GRANT OPTION;
# 注意,这里和mysql可能有一点不同,必须加上 WITH GRANT OPTION,否则 用户添加的权限一直都是USAGE,USAGE不是实际的权限,因此是无效的,

# 修改数据库用户密码
SET PASSWORD FOR 'user'@'localhost' = PASSWORD('passwd');
# 这里和mysql也不一样,mysql修改用户密码使用的alter语句,这里也需要注意。

# 查看用户权限
SHOW GRANTS FOR 'username'@'localhost';

# 刷新权限
FLUSH PRIVILEGES;

~~ 3. 安装php5 (debian8目前stable repository中还没有加入php7) ~~

~~ apt-get install php5 php5-curl php5-gd php5-mysql php5-xmlrpc php5-json ~~

2019年10约15日更新

PHP - FCGI 运行模式 + 切换底层服务器环境由 Apache 到 Nginx

Ngix 和 Apache 两者的性能差异是在架构层面上的。Nginx 运行在异步模式下,要比 Apache 的多线程要消耗更少的系统资源,且更快的响应请求,在我的阿里云小鸡上,对性能的节省就显得很重要。因此我打算将服务器环境切换为 Nginx .


# 1. 创建网站文件的存储目录
mkdir -p /var/www/html/example.com/public_html

# 1. 创建相关的日志文件目录,以方便后期检查与维护
# 创建 NGINX 日志目录
mkdir -p /var/log/nginx
# 创建 PHP 运行日志目录
mkdir -p /var/log/fpm-php

# 2. 安装与配置 NGINX 及相关模块
apt-get install nginx fcgiwrap nginx-doc  ssl-cert
# 3. 查看 Nginx 状态
nginx -V

# 3. 安装最新的 PHP 7.3, 由于 Debian 最新的 Testing 仓库已有 php7.3版本,因此这里我们直接从仓库安装
# 3.1 添加 Debian Testing 的数据源
echo -e '#Adding Testing Repository\n deb http://mirrors.aliyun.com/debian/ testing main'  >>  /etc/apt/sources.list
# 3.2 安装 PHP 7.3
apt install php7.3-cli  php7.3-fpm php7.3-common php7.3-json php7.3-readline

php7.0比php5.6版本有了大幅度的性能提升,而7.3比7.1还有20%的性能提升,因此我们需要使用 \* debian stretch \* 下 testing repositor 的最新7.3稳定版, 并且最终php运行模式为php-fpm。

# 3.2.1 设置默认的 PHP-FPM运行日志的输出目录
vi /etc/php/7.3/fpm/php-fpm.conf
# 将 24 行 error_log 设置修改为 以下位置
error_log = /var/log/fpm-php/php7.3-fpm.log

# 3.2.2 PHP 运行的默认设置
# php.ini 使用系统的默认设置已经比较合适,这里就不做修改了

# 3.2.3 针对站点的pool设置
原则上每个站点对应一个php-fpm pool
pool配置文件目录/etc/php/7.3/fpm/pool.d/example.com.conf
[example.com]
prefix = /var/www/html/example.com
user = www-data
group = www-data
listen = /run/php/example.com.php.sock
listen.owner = www-data
listen.group = www-data
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
request_terminate_timeout = 300s
request_slowlog_timeout = 10s
chdir = /var/www/html/example.com/public_html
slowlog = /var/log/fpm-php/$pool.log.slow
php_flag[display_errors] = off
php_admin_value[display_errors]=0
php_admin_value[display_startup_errors]=0
php_admin_value[html_errors]=0
php_admin_value[define_syslog_variables]=0
php_admin_flag[file_uploads]=1
php_admin_flag[log_errors] = on
php_admin_value[log_errors]=1
php_admin_value[upload_tmp_dir]="/var/tmp"
php_admin_value[upload_max_filesize]="4M"
php_value[max_input_time]="120"
php_admin_value[max_input_time]=120
php_value[max_execution_time]="300"
php_admin_value[post_max_size]="4M"
php_value[session.gc_maxlifetime]=3600
php_admin_value[session.gc_probability]=1
php_admin_value[session.gc_divisor]=100
php_admin_value[error_log] = /var/log/fpm-php/example.com.log
php_admin_value[memory_limit] = 64M
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
php_admin_value[magic_quotes_gpc]=0
php_admin_value[register_globals]=0
php_admin_value[session.auto_start]=0
php_admin_value[mbstring.http_input]="pass"
php_admin_value[mbstring.http_output]="pass"
php_admin_value[mbstring.encoding_translation]=0
php_admin_value[expose_php]=0
php_admin_value[allow_url_fopen]=1
php_admin_value[safe_mode]=0
php_admin_value[cgi.fix_pathinfo]=1
php_admin_value[cgi.discard_path]=0

# 测试 example.com 的 fpm-php 配置文件是否正确
php-fpm7.3 -y /etc/php/7.3/fpm/pool.d/example.com.conf -t
  1. 服务器安全性提升(安全性优化)

@TODO这里暂时略过,有时间再写

+ ssh配置 (禁用部分账户 + ssh key登录) 
  1. 建立网站文件目录

mkdir -p /var/www/domain.com/html/

  1. 安装certbot证书签发程序

# 安装基本库
apt-get install gcc build-essential autoconf openssl

# 下载certbot最新版客户端
git clone https://github.com/certbot/certbot.git

# 解压后,运行目录下的certbot-auto安装certbot客户端
./cerbot/certbot-auto --install-only

# 使用最新版本的ACME2特性签发wildcard域名证书
# 注意wildcard证书只针对子域名,而根域名也需要签发证书,所以以下包含两个域名
./certbot-auto --server https://acme-v02.api.letsencrypt.org/directory -d *.domainexample.com -d domainexample.com --manual --preferred-challenges dns-01 certonly

# 在证书签发的过程中需要添加DNS TXT解析记录,已验证域名所有权,这里需要注意
# 签发成功后出现以下提示

 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/domainexample.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/domainexample.com/privkey.pem
   Your cert will expire on 2018-07-01. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot-auto
   again. To non-interactively renew *all* of your certificates, run
   "certbot-auto renew"

# 如果因为一些原因而错误签发了证书,可以使用revoke命令撤销

# 如果你使用的是cert-only子命令获取的证书,这需要使用证书文件和私钥来撤销证书

./certbot-auto revoke --cert-path /etc/letsencrypt/archive/domainexample.com/cert1.pem --key-path  /etc/letsencrypt/archive/domainexample.com/privkey1.pem

2018年06月26日补充内容

由于LE的证书有效期只有三个月,因此不到三个月就要对证书进行renew,这就是免费证书所需要付出的代价,好在FE证书支持全站子域名的wildcard证书,三个月renew一次也就忍忍吧,不服和图方便的话,一年花个100刀买证书吧,毕竟钱花哪哪好。

由于目前我使用的就是wildcard证书,为wildcard证书需要连接acme-v02进行验证,并且wildcard目前只支持dns-01的授权方式,因此就不得不每次手动的manual来进行证书的renew。Renew的方法不是用certbot-auto的renew命令,因为是手动的,就需要使用certonly命令。后期考虑结合VPS服务商的DNS API来自动解决这个问题。

./certbot-auto \
--server https://acme-v02.api.letsencrypt.org/directory \
-d *.domainexample.com \
-d domainexample.com \
--manual \
--preferred-challenges dns-01 \
certonly

# 命令运行后会提示添加 TXT 文本格式的 DNS解析记录
# 注意由于添加证书的域名是根域名+wildcard子域名,因此需要添加2条DNS记录。
# 不然的话一直会验证不通过。

# 验证通过后顺利生成新的证书,并且/etc/letsencrypt/live/domainexample.com/目录下的证书指向软连接已经被成功更新。
# 重启或是reload apache服务,就会发现证书顺利的rewnew和更新了。

# service apache2 restart 或者 service apache2 reload

~~ 7. clone与修改apache的配置文件 ~~


# 443 安全连接与端口配置文件
# 注意事项
# 1. ssl的配置文件名和ssl的配置文件选项

# 根域名跳转到www
<VirtualHost *:443>
    ServerName domainexample.com
    Redirect permanent / https://www.domainexample.com/

    # 即使根域名全站跳转,也必须要明确的支出证书文件的路径,否则就会出现文章底部的错误。
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/domainexample.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/domainexample.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/domainexample.com/fullchain.pem
</VirtualHost>

<VirtualHost *:443>
    ServerAdmin admin@domainexample.com
    ServerName www.domainexample.com
    DocumentRoot /var/www/html/domainexample.com/public_html/
    ErrorLog /var/www/html/domainexample.com/logs/error.log
    CustomLog /var/www/html/domainexample.com/logs/access.log combined

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/domainexample.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/domainexample.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/domainexample.com/fullchain.pem
</VirtualHost>

# 2. 80端口的配置文件

# Redirect all 80 port traffic to 443 instead

<VirtualHost *:80>
     ServerAdmin admin@domainexample.com
     ServerName domainexample.com
     ServerAlias www.domainexample.com
     DocumentRoot /var/www/html/domainexample.com/public_html/
     ErrorLog /var/www/html/domainexample.com/logs/error.log
     CustomLog /var/www/html/domainexample.com/logs/access.log combined

    <IfModule mod_rewrite.c>
            RewriteEngine on
            RewriteCond %{SERVER_PORT} !^443$
            RewriteRule (.*) https://%{SERVER_NAME}/$1 [R]
    </IfModule>

</VirtualHost>

# 上面的这种操作是一种heavy lift 如果需要跳转的链接成千上万,服务器就会有很重的负载
# 针对80端口的流量跳转
# 使用virtualhost的redirect属性设置比较优雅和高效
# 将所有根域名下的流量跳转到www
<VirtualHost *:80>
    ServerName domainexample.com
    Redirect permanent / https://www.domainexample.com/
</VirtualHost>

# 另外还需要在ServerName domainexample.com

<VirtualHost *:80>
    ServerName www.domainexample.com
    # 由于domainexample.com已经被设置为一个独立的virtual host
    # 因此下方的ServerAlias设置项必须删除,这里我们直接注释掉
    # ServerAlias domainexample.com
    ServerAdmin admin@domainexample.com
    DocumentRoot /var/www/html/domainexample.com/public_html
    # 将所有80端口流量重定向到443端口
    Redirect permanent / https://www.domainexample.com/

    DocumentRoot /var/www/html/domainexample.com/public_html/
    ErrorLog /var/www/html/domainexample.com/logs/error.log
    CustomLog /var/www/html/domainexample.com/logs/access.log combined
</VirtualHost>
# 3. 激活Apache的ssl模块
a2enmod ssl

# 这样与ssl相关的依赖模块也会被激活
# 之后重启服务器就可以愉快的享受ssl带来的安全性了。

service apache2 restart

# @TODO 以下有时间再补

# Apache配置与服务器性能优化
# 1. 提升安全性
# 2. 开启流量压缩
# 3. 并行下载,静态资源分离 
# 4. 禁用不安全的HTTP协议

企业邮箱配置

企业邮箱的话,如果你的公司有营业执照和公章,则可以通过和阿里的OA平台 - 钉钉 签约,然后使用阿里云提供的免费企业邮箱服务,50个账户,每个账户5G容量,对于人数不多的公司完全够用了,一年可以省下将近2000块钱的费用。按照指引注册的话,快的话一天就可以审核通过使用了。

注意这里面也是有坑的。

  1. 你的域名无法直接绑定到钉钉提供的免费企业邮箱,必须按照钉钉的提示先申请一个 company-name.onaliyun.com的二级域名,然后企业邮箱的功能算是激活了。
  2. 进入钉钉手机端,钉邮 -> 【邮箱管理】-> 【绑定新域名】-> 添加自己公式的域名,然后后台会提示需要你添加DNS解析记录以验证域名所有权。
  3. 第二步是在域名的DNS解析页面上设置mx解析记录,解析到mxw.hichina.com和mxn.hichina.com,priority优先级分别是5和10。这两个服务器是阿里企业邮箱的DNS服务器。除此之外还要添加一个CNAME的DNS记录,主机为mail,地址为mail.mxhichina.com。由于MX记录同步比较慢,通常需要1-2个小时才会生效。
  4. 验证通过后自己的域名就会现在在邮箱的管理页面中,在钉钉手机端后台选中自己的域名设置为默认,这样所有人的企业邮箱都会变成自己域名的后缀。
  5. 走完这一步,就可以在钉钉的通讯录中邀请公司的同事加入组织,并为他们配置企业邮箱账户。
  6. 然而即使你为每个人都配置完企业邮箱了,这个时候你仍然没有办法使用客户端来收信。什么?你就是为了骗企业邮箱和邮件客户端收信这个来的?别急。即使你用foxmail添加了邮箱的地址,你输入了钉钉的账户密码,IMAP/SMTP端口什么的你都配置正确,你还是收不到信。那么怎么办?你需要在钉钉的后台 【通讯录】->【邮箱账户管理】->【个人邮箱】->【重设密码】为每个邮箱账户重设密码,然后每个人都登陆一下阿里企业邮箱网页版 https://qiye.aliyun.com 修改密码。因为企业邮箱的登录密码,不是钉钉账户的密码。所以用钉钉账户的密码是没有办法在邮箱客户端登陆邮件服务器的。改完密码之后使用新密码配置邮件客户端,一切都正常了。而搞笑的是,打电话给钉钉的客服,客户告诉我免费账户不支持IMAP客户端收信,嗯嗯,你说的对,但是我不信。

2018年7月4日补充更新

当你打开网站的https页面出现错误SSL_ERROR_RX_RECORD_TOO_LONG(firefox)或是发送的响应无效 ERR_SSL_PROTOCOL_ERROR (Chrome)时, 且测试 http://www.domain.com:443 可以访问,说明http运行在443端口。

且你确信证书的签发没有问题,且netstat -npl 列出的端口也是开放的,因此出问题的就是apache2的配置文件,准确的说应该是site-available目录下的配置文件有问题。

注意无论是哪个server name下来的站点配置文件,即使全站跳转,配置文件也必须明确的指除证书文件的具体路径,否则就会出现上面的问题。解决办法就是在每一个virtualhost *:443 配置项下都必须明确指出SSL证书相关文件的路径。

配置项的问题解决之后,service apache2 reload 一切就正常了。

Drupal 7 问题汇总

  1. cron无法自动运行的问题

Cron无法自动运行,而可以点击cron链接手动运行。出现这个问题的原因很可能是drupal系统变量中的install_task没有被设置为完成。

解决办法:

drush vset install_task done

  1. 使用第三方libraries模块时,libraries目录下已存放第三方库文件,且后台libraries的日志显示第三方库(JS/CSS)文件已经正常安装,或是使用drush lls命令显示第三方库也显示正常,但是在renderable array中使用 $renderable_array['#attached']['libraries_load'][] = array('third_party_libs_name') 却无法载入第三方库的情况
$renderable_array['#attached']['libraries_load'][] = array('third_party_libs_name')

使用这种方式有几个前提,而官网的document却没有完全说明这一点

1.该方法的应用对象为 renderable array, 即为元数据的数组
2.该 renderable_array 必须有 #theme 键指定一个 主题层的生成(render)函数
针对以上的情况, ['#attached']['libraries_load'] 的适用情况为比较小的页面元素,如 block、form 或是 markup

具体的原因为当主题函数在解析 renderable array 为遇到libraries_load时,会调用 libraries_load()方法,并传入 'third_party_libs_name' 作为参数,这样相关的第三方库文件 js/css 文件就会追加到页面的 header.

举个例子

假如需要某一个页面添加第三方前端库,可能需要使用 hook_page_build(&$page) 或是 hook_page_alter(&$page), 然而在 drupal 中 其实是没有 theme_page() 这个函数的,页面的渲染方式实际上通过预处理函数 preprocesser,然后将 $page 分割成小的变量,最终在tpl.php的前端模板中打印出来,所以hook_page_build(&$page) 或是 hook_page_alter(&$page) 是无法使用 #attched 这种方式。针对这种情况,比较方便的方法是直接调用 libraries_load() 方法来添加第三方前端库。

除此之外 可以针对 页面中小的的构成元素,如 block 或是 markup 的 renderable array 中追加 ['#attached']['libraries_load'] 来加载第三方前端库。

学习SQL的方法

比较好的学习办法是打开phpmyadmin的console窗口
在浏览器界面对数据库进行操作,对照console中的SQL语句

数据库结构修改

# 建立新的数据库表
CREATE TABLE IF NOT EXISTS `database_name`.`table_name`(
    `id` INT UNSIGNED AUTO_INCREMENT,
    `Field1` VARCHAR(8) NOT NULL,
    `Field2` VARCHAR(4) NOT NULL,
    `Field3` INT(1) NOT NULL,
    PRIMARY KEY ( `id` ),
)ENGINE=InnoDB CHARSET=utf8 COLLATE utf8_unicode_ci;

# 修改数据表的名称
RENAME TABLE `database_name`.`table_name` TO `database_name`.`new_table_name`;

数据导出

# 导出整个数据库
mysqldump -u user -p database > database.sql
# 导出单个数据表 (包括表结构和表数据)
mysqldump -u user -p database table > table.sql
# 仅导出数据表中的数据 -t 参数
mysqldump -u user -p -t database table > table.sql
# 仅导出数据表中的表结构 -d 参数
mysqldump -u user -p -d database table > table.sql

数据导入

# 导入整个数据库
mysql -u user -p database < database.sql

在导入导出表的时候,如果开发和生产服务器的的数据库名称或是用户不同,建议导出的时候仅导出开发服务器数据库表的数据,然后在生产服务器使用mysql的source命令来导入数据。

# 查看数据库中是否含有表
show tables like 'table_name';

错误提示

目录可能被另一个进程锁定或被设置为只读.
目录: ‘/Users/user-name/Library/Application Support/Autodesk/AutoCAD LT 2019 Standalone/R23.0/local/@zh_CN@’

错误原因

汉化补丁仅仅是对系统的界面进行了汉化,没有汉化AutoCAD启动向导页面的模板文件,因此/Users/user-name/Library/Application Support/Autodesk/AutoCAD LT 2019 Standalone/R23.0/local/目录下没有相关的简体中文的模板文件,CAD找不到目录,就认为CAD对 @zh_CN@文件夹 没有相关的权限,因此就报了一个只读的权限错误。

解决办法

这里只给命令行截面下的解决办法

# 1. 打开MAC的命令行工具
# 2. 定位到local语言目录下
cd /Users/user-name/Library/Application Support/Autodesk/AutoCAD LT 2019 Standalone/R23.0/local/
# 注意 /Users/user-name 中 user-name是你的用户名
# 3. 将英文版的向导模板文件拷贝为简体中文版
cp  -r \@en\@/  \@zh_CN\@
# 注意 这不是一种优雅的fix,真正的解决办法有待于官方release完整的中文语言包
# 本解决办法仅能解决安装简体中文版本后无法启动的问题
# 重新打开AutoCAD之后,中文截面就有了,就可以正常启动了。