docker 快速部署Odoo环境

写在前面

经常需要将开发的项目部署到测试服务器上以便客户可以看到,但之前都是安装环境这样的方式,不适合快速搭建。

安装Docker

yum install -y docker

systemctl enable docker # 开机自动启动docker
systemctl start docker # 启动docker
systemctl restart docker # 重启dokcer
systemctl stop docker

# 验证docker是否安装成功
docker run --rm hello-world

设置Docker 加速

阿里云:https://cr.console.aliyun.com/#/accelerator

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://wkumpv63.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

运行 PostgreSQL server

docker仓库:https://hub.docker.com/_/postgres/

这里使用的是latest,即当前时间的10.4版本

docker run -d -e POSTGRES_USER=odoo -e POSTGRES_PASSWORD=odoo --name actt postgres:latest

运行Odoo

# 运行odoo实例
docker pull odoo
docker run -p 8069:8069 --name odoo --link actt:db -t odoo

# 停止或重启odoo实例
docker stop odoo
docker start -a odoo

使用自定义配置运行Odoo

服务器的默认配置文件位于(/etc/odoo/openerp-server.conf),若你有一个自定义的配置文件位于(/path/to/config/openerp-server.conf),那么你的运行命令就该是如下这样。

docker run -v /path/to/config:/etc/odoo -p 8069:8069 --name odoo --link actt:db -t odoo

当然,你也可以直接指定内联的Odoo参数,这些参数必须在前面加上 --,如下所示:

docker run -p 8069:8069 --name odoo --link db:db -t odoo --db-filter=odoo_db_.*

当然,运行Odoo,大部分时候是需要运行我们自己开发的模块,在docker中运行的时候,我们可以使用如下方式将其加载。

docker run -v /path/to/addons:/mnt/extra-addons -p 8069:8069 --name odoo --link db:db -t odoo

防火墙设置

firewall-cmd --zone=public --add-port=8069/tcp --permanent
firewall-cmd --reload

Docker 维护命令

# 查看容器信息
docker info

# 查看镜像列表
docker images

# 启动、停止
docker stop <id/container_name>
docker start <id/container_name>
docker kill <id/container_name>

# 查看正在运行的容器
docker ps -a

# 查看容器日志
docker logs <id/container_name>

# 实时查看日志输出
docker logs -f <id/container_name> (类似 tail -f) (带上时间戳-t)

# 启动容器以后台方式运行(更通用的方式)
docker run -d -it  image_name

# 显示一个运行的容器里面的进程信息
docker top Name/ID  

# 移除镜像
docker stop <id/container_name>
docker rm  <id/container_name> 

# 制作镜像(根据容器id来创建新的镜像
docker commit 93639a83a38e  wsl/javaweb:0.1

Odoo Logging changes(消息通知机制)

最近在开发一个项目管理系统,客户希望某些字段在改变的时候记录下这个字段的改变信息。这时候就可以使用Odoo的消息通知机制。

官方文档:https://www.odoo.com/documentation/10.0/reference/mixins.html#logging-changes

一、引入消息模型

class Modle(models.Model):
    _name = "demo"
    _inherit = ['mail.thread']

在form中增加消息显示代码

<form>
   ....
   省略一串代码
   ....
    <div class="oe_chatter">
        <field name="message_follower_ids"
               widget="mail_followers"/>
        <field name="message_ids" widget="mail_thread"/>
    </div>
</form>

二、定义追踪属性

在模块引入了消息机制模型后,track_visibility属性即可使用,它支持如下两种写法。

  1. track_visibility='always'
  2. track_visibility='onchange'

定义字段改变显示的内容

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data noupdate="True">
		<!-- mail-->
		<record id="mt_state_change" model="mail.message.subtype">
			<field name="name">confirmed</field>
			<field name="res_model">demo</field>
			<field name="default" eval="True"/>
			<field name="description">项目状态变更为正式立项!</field>
		</record>
    </data>
</odoo>
class Modle(models.Model):
    _name = "demo"
    _inherit = ['mail.thread']

    state = fields.Selection(selection=[('draft', 'Draft'), ('confirmed', 'Confirmed'), ], default='draft', track_visibility='onchange')
    name = fields.Char(string="IP name", required=True, track_visibility='always')

    # 重写该方法,在合适的条件下调用前面声明的消息内容
	def _track_subtype(self, init_values):
	self.ensure_one()
	if 'state' in init_values and self.state == 'confirmed':
		return 'project_manager.mt_state_change'
	return super(Modle, self)._track_subtype(init_values)

三、效果

搭建ngrok服务器

一、前提条件

一台有公网ip的服务器,一个域名(二级也行),能正确解析到服务器

二、准备工作

安装依赖

yum -y install gcc git build-essential golang mercurial

三、搭建Ngrok

下载源码、生成证书、替换证书

git clone https://github.com/tutumcloud/ngrok.git ngrok

# 定义ngrok使用的域名
NGROK_DOMAIN="ngrok.bcsytv.com"

# 生成需要的证书
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$NGROK_DOMAIN" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=$NGROK_DOMAIN" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

# 替换证书
cp rootCA.pem assets/client/tls/ngrokroot.crt
cp device.crt assets/server/tls/snakeoil.crt
cp device.key assets/server/tls/snakeoil.key

编译ngrok服务端

# Linux系统GOOS=linux,64位系统GOARCH=amd64,32位系统GOARCH=386
# 当前系统可用go env查看

GOOS=linux GOARCH=amd64
make release-server

启动服务端

./bin/ngrokd -tlsKey="assets/server/tls/snakeoil.key" -tlsCrt="assets/server/tls/snakeoil.crt" -domain="ngrok.bcsytv.com" -httpAddr=":8081" -httpsAddr=":8082" -tunnelAddr=":8083"

注册为服务,建立服务文件 /usr/lib/systemd/system/ngrok.service 将如下代码写入(根据自己的目录进行替换)

[Unit]
Description=Share local port(s) with ngrok
Documentation=https://ngrok.com/docs
After=syslog.target network.target

[Service]
PrivateTmp=true
Type=simple
ExecStart=/root/ngrok/bin/ngrokd -tlsKey=/root/ngrok/assets/server/tls/snakeoil.key -tlsCrt=/root/ngrok/assets/server/tls/snakeoil.crt -domain=ngrok.bcsytv.com -httpAddr=:8081 -httpsAddr=:8082 -tunnelAddr=:8083
ExecStop=/usr/bin/killall ngrok

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
# 设置开机启动
systemctl enable ngrok.service
systemctl start ngrok.service
systemctl stop ngrok.service
systemctl status ngrok.service

编译客户端

# Windows x64
GOOS=windows GOARCH=amd64 make release-client

# mac
GOOS=darwin GOARCH=amd64 make release-client

Postgresql pg_dump: 因为服务器版本不匹配而终止

之前在生产环境部署的Postgresql为9.6版本,但最近在备份数据的时候发现他所使用的客户端居然是9.2的版本。。

# 查看已安装的postgresql程序
yum list installed |grep postgresql

# 查看Yum已安装的源
yum repolist

# 引入postgresql对应版本的源
# http://yum.postgresql.org/repopackages.php
rpm -Uvh https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm

# 查找需要的版本
yum search postgresql --enablerepo=pgdg96

# 安装对应版本的客户端
yum install -y postgresql96

# 查看执行程序路径
find / -name pg_dump -type f

# 替换低版本
ln -snf /usr/pgsql-9.6/bin/pg_dump /usr/bin/pg_dump

备份命令

su - postgres -c "pg_dump -U odoo -Fc -d Yunshang -f Yunshang.dump"

恢复命令

createdb -U odoo Yunshang
pg_restore -v -U odoo "F:\python\YunShangZhuangShi\Yunshang.dump"

DataTable 差集Except、交集Intersect、并集Union

使用方法

//获取第一个数据源DataTable
DataTable _dtSource = DBHelper.GetDataTable("select top 10  ksdid,user_id,user_pwd from ksd_user_info");

IEnumerable<DataRow> query = _dtSource.AsEnumerable().Where(t => t.Field<string>("user_id").StartsWith("66")).ToList();
//获取第二个数据源DataTable
DataTable _dt2 = query.CopyToDataTable();

//比较两个数据源的交集
IEnumerable<DataRow> query2 = _dtSource.AsEnumerable().Intersect(dt2.AsEnumerable(), DataRowComparer.Default);
//两个数据源的交集集合      
DataTable dt3 = query2.CopyToDataTable();


//获取两个数据源的并集
IEnumerable<DataRow> query2 = _dtSource.AsEnumerable().Union(dt2.AsEnumerable(), DataRowComparer.Default);
//两个数据源的并集集合
DataTable dt3 = query2.CopyToDataTable();


//获取两个数据源的差集
IEnumerable<DataRow> query2 = _dtSource.AsEnumerable().Except(dt2.AsEnumerable(), DataRowComparer.Default);
//两个数据源的差集集合
DataTable dt3 = query2.CopyToDataTable();

自定义比较器

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Kelun.Comparer
{
    /// <summary>
    /// 计量单位比较器
    /// </summary>
    class JldwComparer : IEqualityComparer<DataRow>
    {
        public bool Equals(DataRow x, DataRow y)
        {
            return (x.Field<string>("F_DWBH") == y.Field<string>("F_DWBH") &&
            x.Field<string>("F_DWMC") == y.Field<string>("F_DWMC") &&
            x.Field<string>("F_DWLX") == y.Field<string>("F_DWLX"));
        }

        public int GetHashCode(DataRow obj)
        {
            return obj.ToString().GetHashCode();
        }
    }
}

根据自定义条件获取差集

// 获取2个数据源的差集
//IEnumerable<DataRow> query = _dt.AsEnumerable().Except(_sdt.AsEnumerable(), DataRowComparer.Default);
IEnumerable<DataRow> query = _dt.AsEnumerable().Except(_sdt.AsEnumerable(), new JldwComparer());

int querynum = query.Count();

if (querynum > 0)
{
	var _saveDt = query.CopyToDataTable();
	SqlDbHelper.Insert(_saveDt, "HNKL_JLDW_ERP");
	var msg = string.Format("计量单位写入成功,共计{0}条", _saveDt.Rows.Count);
	Logger.Info(msg, null, "System");
}

Quartz.NET 3.x 例子

最近写项目需要用到定时任务,所以就找到了Quartz.NET,但网上的例子都是到处抄袭的,完全没有适合Quartz.NET 3.x 的有用信息,这里就将自己的使用例子帖一下。

  • IDE:VS2017
  • .Net : 4.5.2

写在前面

Quartz.NET 在3.x已经将插件分离了,所以如果要从xml直接加载文件,需要引入插件包

引入包

配置

app.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <configSections>
    <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </configSections>

  <quartz>
    <add key="quartz.scheduler.instanceName" value="QuartzScheduler"/>
    <add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz"/>
    <add key="quartz.threadPool.threadCount" value="10"/>
    <!--******************************Plugin配置*********************************************-->
    <add key="quartz.plugin.jobInitializer.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins"/>
    <add key="quartz.plugin.jobInitializer.fileNames" value="quartz_jobs.xml"/>
  </quartz>

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
  </startup>

</configuration>

quartz_jobs.xml

<?xml version="1.0" encoding="UTF-8"?>

<!-- This file contains job definitions in schema version 2.0 format -->

<job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">

  <processing-directives>
    <overwrite-existing-data>true</overwrite-existing-data>
  </processing-directives>


  <schedule>

    <!--开始执行一个调度-->
    <job>
      <name>jldwjob</name>
      <group>kelun</group>
      <description>计量单位</description>
      <job-type>Kelun.JldwJob, Kelun</job-type><!--格式:实现了IJob接口的包含完整命名空间的类名,程序集名称-->
      <durable>true</durable>
      <recover>false</recover>
    </job>
    
    <trigger>
      <cron>
        <name>jlwd</name>
        <group>kelun</group>
        <job-name>jldwjob</job-name>
        <job-group>kelun</job-group>
        <!--<start-time>2018-01-22T00:00:00+08:00</start-time>-->
        <cron-expression>0/1 * * * * ?</cron-expression><!--每3秒执行一次-->
      </cron>
    </trigger>

  </schedule>
</job-scheduling-data>

接口实现

using Kelun.Log4Net;
using Quartz;
using System.Reflection;
using System.Threading.Tasks;

namespace Kelun
{
    class JldwJob : IJob
    {

        private static readonly IMyLog Logger = MyLogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        public Task Execute(IJobExecutionContext context)
        {
            return Task.Run(() => 
            {
                Logger.Info("定时任务执行");
            });
        }
    }
}

启动Quartz

public MainForm()
{
    InitializeComponent();
    RunProgramAsync().GetAwaiter().GetResult();
}

private static async Task RunProgramAsync()
{
    try
    {
        StdSchedulerFactory factory = new StdSchedulerFactory();

        IScheduler scheduler = await factory.GetScheduler();

        //开启调度器
        await scheduler.Start();

        //创建一个作业
        //IJobDetail job = JobBuilder.Create<JldwJob>().WithIdentity("myJob", "group1").Build();

        //ITrigger trigger = TriggerBuilder.Create()
        //    .WithIdentity("myTrigger", "group1")
        //    .StartNow()     //现在开始
        //    .WithSimpleSchedule(x => x
        //        .WithIntervalInSeconds(1)   //触发时间,1秒一次
        //        .RepeatForever())
        //    .Build();

        //把作业,触发器加入调度器。
       //await scheduler.ScheduleJob(job, trigger);  

        //await scheduler.Shutdown();

    }
    catch (SchedulerException se)
    {
        Logger.Error("执行错误", se);
    }
}

禁用Quartz.NET日志输出

<logger name="Quartz">
	<level value="OFF" />
</logger>