分类 Web 下的文章

支持伪静态

为了使typecho支持伪静态,即访问文章时浏览器路径不显示index.php,我们可以在nginx配置文件中按照下面设置方法进行设置:

location / {
    index index.html index.php;
    if (-f $request_filename/index.html) {
        rewrite (.*) $1/index.html break;
    }
    if (-f $request_filename/index.php) {
        rewrite (.*) $1/index.php;
    }
    if (!-f $request_filename) {
        rewrite (.*) /index.php;
    }
}

支持SSL

前提先准备好自己ssl证书。(可以通过Let's Encrypt申请免费的证书)。
nginx配置文件配置

server {
    listen 80;
    server_name www.domain.com;
    ## 301 跳转到https
    return 301 https://$host$request_uri;
}

server {
    server_name www.domain.com;
    root /var/www/project;
    index index.php index.html index.htm;
    
    location ~* \.(gif|jpg|png|jpeg|ico)$ {
        expires max;
    }

    ## 支持伪静态
    location / {
    index index.html index.php;
    if (-f $request_filename/index.html) {
        rewrite (.*) $1/index.html break;
    }
    if (-f $request_filename/index.php) {
        rewrite (.*) $1/index.php;
    }
    if (!-f $request_filename) {
        rewrite (.*) /index.php;
    }
    }

    location ~ .*\.php(\/.*)*$ {
    # fastcgi_index   index.php;
    include snippets/fastcgi-php.conf;
        fastcgi_pass 127.0.0.1:9000;
    # fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        # include fastcgi_params;
    }

    listen 443 ssl;
    ssl_certificate cert/domain.com.cert;
    ssl_certificate_key cert/domain.com.key;
    include cert/options-ssl-nginx.conf;
    ssl_dhparam cert/ssl-dhparams.pem;
}

上面的options-ssl-nginx.conf内容为:

ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;

ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS";

背景:在业务中,经常需要在执行数据库操作后(事务提交完成),发送消息或事件来异步调用其他组件执行相应的业务操作。
比如:用户注册成功后,发送激活码或激活邮件,如果用户保存后就执行异步操作发送激活码或激活邮件,但是前面用户保存后发生异常,数据库进行回滚,用户实际没有注册成功,但用户收到激活码或激活邮件。此时,我们就迫切要求数据库事务完成后再执行异步操作。

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);

    // 发送激活码或激活邮件
    jmsProducer.sendEmail(user.getId());
}

// -------------------------------------
public void sendEmail(int userId) {
    /*
     * 获取待接收邮件的用户。(如果上面的保存用户方法还未提交事务,则实际数据还未插入到数据库中,此时会返回null)
    */
    User user = userDao.get(userId);
    // 可能抛NullPointException
    String email = user.getEmail();
    mailService.send(email);
}

解决方案

1、Spring 4.2之后,使用注解@TransactionalEventListener

/**
 * 业务Service
 */
@Service
@Transactional
public class FooService {
    @Autowired
    private  ApplicationEventPublisher applicationEventPublisher;

    public User saveUser(User user) {
        userDao.save(user);
        // 注册事件
        applicationEventPublisher.publishEvent(new SavedUserEvent(user.getId()));
    }
}

// -------------------------------------
/**
 * 保存用户事件
 */
public class SavedUserEvent {
    private int userId;
    
    public SavedUserEvent(int userId) {
        this.userId = userId;
    }
    
    // getter and setter
}

// ---------------------------------
/**
 * 事件侦听,处理对应事件
 */
@Component
public class FooEventListener() {
    @Autowired
    private UserDao userDao;
    @Autowired
    private MailService mailService;

    @TransactionalEventListener
    public sendEmail(SavedUserEvent savedUserEvent) {
        User user = userDao.get(userId);
        String email = user.getEmail();
        mailService.send(email);
    }
}

2.使用TransactionSynchronizationManager 和 TransactionSynchronizationAdapter

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);
    final int userId = user.getId();

    // 事务提交后调用
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            jmsProducer.sendEmail(userId);
        }
    });
}

注意:上面的代码将在事务提交后执行.如果在非事务context中将抛出java.lang.IllegalStateException: Transaction synchronization is not active。
改进后代码:

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);
    final int userId = user.getId();

    // 兼容无论是否有事务
    if(TransactionSynchronizationManager.isActualTransactionActive()) {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                jmsProducer.sendEmail(userId);
            }
        });
    } else {
        jmsProducer.sendEmail(userId);
    }
}

3.在上面2的基础上扩展TransactionSynchronizationAdapter

@Component("afterCommitExecutor")
public class AfterCommitExecutor extends TransactionSynchronizationAdapter implements Executor {
    private static final ThreadLocal<List<Runnable>> RUNNABLES = new ThreadLocal<List<Runnable>>();
    private ThreadPoolExecutor threadPool;

    @PostConstruct
    public void init() {
        Logs.debug("初始化线程池。。。");
        int availableProcessors = Runtime.getRuntime().availableProcessors();
        if (0 >= availableProcessors) {
            availableProcessors = 1;
        }
        int maxPoolSize = (availableProcessors > 5) ? availableProcessors * 2 : 5;
        Logs.debug("CPU Processors :%s MaxPoolSize:%s", availableProcessors, maxPoolSize);
        threadPool = new ThreadPoolExecutor(
            availableProcessors,
            maxPoolSize,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(maxPoolSize * 2),
            Executors.defaultThreadFactory(),
            new RejectedExecutionHandler() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    Logs.debug("Task:%s rejected", r.toString());
                    if (!executor.isShutdown()) {
                        executor.getQueue().poll();
                        executor.execute(r);
                    }
                }
            }
        );
    }

    @PreDestroy
    public void destroy() {
        Logs.debug("销毁线程池。。。");
        if (null != threadPool && !threadPool.isShutdown()) {
            threadPool.shutdown();
        }
    }

    @Override
    public void execute(@NotNull Runnable runnable) {
        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            runnable.run();
            return;
        }
        List<Runnable> threadRunnables = RUNNABLES.get();
        if (threadRunnables == null) {
            threadRunnables = new ArrayList<Runnable>();
            RUNNABLES.set(threadRunnables);
            TransactionSynchronizationManager.registerSynchronization(this);
        }
        threadRunnables.add(runnable);
    }

    @Override
    public void afterCommit() {
        Logs.debug("事务提交完成处理 ... ");
        List<Runnable> threadRunnables = RUNNABLES.get();
        for (int i = 0; i < threadRunnables.size(); i++) {
            Runnable runnable = threadRunnables.get(i);
            try {
                threadPool.execute(runnable);
            } catch (RuntimeException e) {
                Logs.error("", e);
            }
        }
    }

    @Override
    public void afterCompletion(int status) {
        Logs.debug("事务处理完毕 .... ");
        RUNNABLES.remove();
    }
}

业务层使用:

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;
@Autowired
private AfterCommitExecutor afterCommitExecutor;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);
    final int userId = user.getId();

    // 使用AfterCommitExecutor
    afterCommitExecutor.execute(new Runnable() {
        @Override
        public void run() {
            jmsProducer.sendEmail(userId);
        }
    });
  
}

参考文章

Spring的TransactionEventListener
Spring Event 事件中的事务控制
Java Code Examples org.springframework.transaction.support.TransactionSynchronizationAdapter
Spring Events | Baeldung
Thinking about IT: Transaction synchronization callbacks in Spring Framework

Linux PHP 7.2.11源码编译安装

说明:

  • 安装php版本为7.2.11
  • php解压后工作空间目录:/data/workspace/php
  • php安装目录:/data/soft/php

1、下载并解压PHP源码

# 下载
wget http://cn2.php.net/distributions/php-7.2.11.tar.gz
# 解压
tar zxf php-7.2.11.tar.gz -C /data/workspace
mv php-7.2.12 php
cd php

2、安装所需依赖和工具

apt-get install libxml2-dev libssl-dev libcurl4-gnutls-dev libwebp-dev libjpeg-dev libpng-dev libmcrypt-dev libicu-dev

- 阅读剩余部分 -

Linux Nginx 1.15.5源码编译安装

说明:

  • 安装nginx版本为1.15.5,其他版本类似
  • nginx解压后工作空间目录:/data/workspace/nginx
  • nginx安装目录:/data/soft/nginx
  • nginx依赖opensslpcrezlib,这些包可以下载解压,但不用安装

1、下载并解压nginx、openssl、pcre、zlib源码

# nginx (https://nginx.org/download/)
wget https://nginx.org/download/nginx-1.15.5.tar.gz
# openssl
wget https://www.openssl.org/source/openssl-1.1.0i.tar.gz
# pcre
wget https://ftp.pcre.org/pub/pcre/pcre-8.42.tar.gz
# zilb
wget https://zlib.net/zlib-1.2.11.tar.gz
# 解压
tar zxf nginx-1.15.5.tar.gz -C /data/workspace
tar zxf openssl-1.1.0i.tar.gz -C /data/workspace
tar zxf pcre-8.42.tar.gz -C /data/workspace
tar zxf zlib-1.2.11.tar.gz -C /data/workspace
# 将nginx目录改名
mv nginx-1.15.5 nginx
cd nginx

2、安装所需依赖和工具

apt-get install make gcc g++ perl libperl-dev

- 阅读剩余部分 -

typecho默认首页加载显示的内容会将文章所有的内容,对于短的文章这样倒影响不大,但是如果遇到文章很长,这样就很头疼:

  • 影响首页加载速度。想想如果有7、8篇长文章还带图片,能很快速的打开吗?
  • 影响阅读者的阅读。如果想看下一篇文章,要往下划很多下。

如果解决呢?

- 阅读剩余部分 -

Extjs Combo控件setValue() 显示ID或无效

针对ExtJS 3.x,不过其他版本原理一样
背景:在需要查看相关信息进行编辑form时,原来form表单中设置好的combo可能需要重新设置值,但在这之前需要加载显示原来的值。但是头疼的是,combo要么显示正常要么显示后台id。

- 阅读剩余部分 -