Python图形界面开发神器-page5.0.3安装程序及安装使用教程

1. 软件使用截图

简单的来说,可以通过此软件完成Python图形化界面设计,软件可以自动生成Python代码,省去了大量的调试界面位置的工作。

file

2. 软件下载安装

2.1 Tcl运行环境下载安装

page软件的运行需要基于Tcl环境,所以先安装Tcl是必须的。
官网Tcl官网下载地址:https://www.activestate.com/products/tcl/downloads/
由于官网下载时需要注册认证,所以我下载好了放在本站上以便大家方便使用:
ActiveTcl-8.6.9.8609.2-MSWin32-x64-5ccbd9ac8

注意:
如果通过官网下载的话,官网上有两个Tcl版本可供下载,运行page5.0.3必须下载ActiveTCL8.6才可以,如果安装的是TCL8.5版本运行page5.0.3时会报invalid command name "try"这样一个错误。

下载完成后,先安装,安装没什么可讲,就是普通的安装过程。

2.2 page5.0.3下载安装

官方在SourceForge上,国内下载可能会比较慢:
http://page.sourceforge.net/
当然,我已经下载好了放在本站上了:
page-5.0.3

安装过程也是普通过程,没什么特别需要说明的。

3. 软件使用

3.1 打开软件

打开桌面上的page图标即可打开软件
file

3.2 拖拽布局

通过拖拽实现页面元素布局,页面元素间存在父元素、子元素之间的关系。元素关系树状结构再左下角显示。这个多试几次就知道是怎么运作了。
file

3.3 生成代码

界面布局完成后,点击主界面菜单栏的【Gen_Python】下的【Generate Python GUI】
file
这时会弹出一个保存文件的窗口,保存的是.tcl的布局文件。后期需要对布局进行调整时,只需要打开这个文件就可以继续工作。

3.4 保存代码

3.3保存tcl文件后,就会弹出一个新页面,这个新页面中就是对应的自动生成的python程序代码:
file

点击左下角【save】按钮,就可以将生成的python代码保存到本地,保存位置默认和3.3里面.tcl文件的保存位置一致。
这时,打开3.3步骤里面.tcl文件的保存位置,就会发现一个和.tcl文件同名但扩展名为.py的文件。

3.5 代码修改

默认情况下,直接运行生成的.py文件会报错,主要错误就在

import page_support

page_support.init(root, top)

这两句上,我的做法是直接删除这两句,然后代码就可以正常运行了。

PHP通过SoapClient调用WebService的wsdl格式接口时的注意事项

1. 前言

话说这是个比较让我长见识的问题。虽然做了这么久的技术,但调用接口一直都是用json。通过WebService调用数据还是第一次接触。
由于自己对WebService不熟悉,所以理所应当的夹起尾巴做人,再不露怯的情况下先研究了一下有关WebService的知识,然后开始照着甲方给的WebService接口文档一个一个的去测试。现在觉得已经出师了,所以把过程记录下。

2. 关于阅读wsdl

wsdl是什么?wsdl怎么撰写?这个更多的是WebService服务器端的事情,也就是说这是提供WebService的人应该考虑的事情,如果你只需要调用WebService,需要了解的内容非常少。只需要基本看懂wsdl就可以了。
wsdl本质上就是XML,其中重要的标签有两个一个是<operation>另一个是<sequence>下的<element>

  • <operation>代表WebService可以调用的方法名称。
  • <sequence>下的<element>代表调用方法时需要的参数。

3. 关于传参的问题

3.1 通过键值对数组向方法传参。

假如我们在调用WebService的时候,需要向方法传参,需要以键值对数组的形式传进去。
为啥要强调这个问题呢?
因为在我拿到的接口文档中,调用接口的方法是以Java形式给出的例子:

public String InvokeIntoBaas(String token, String docId, String DocType)

于是乎我也顺理成章的在php中用下面的写法:

<?php
$client = new \SoapClient('http://XXXXX/XXXX?wsdl');

$token = "asd87a98s4ga515sd3ga12ga897sd";
$docId = "32";
$DocType = "tscDoc";

$result = $client->InvokeIntoBaas($token, $docId, $DocType);
var_dump($result);
?>

然后你会发现,这样肯定是无法运行成功的。
向方法传值,一定是通过键值对数组,数组里面写明参数名称和参数值,也就是说无论有多少个参数,都是向方法里面传一个键值对数组就够了。
就像下面这种方式:

<?php
$client = new \SoapClient('http://XXXXX/XXXX?wsdl');

$param = [];
$param["token"] = "asd87a98s4ga515sd3ga12ga897sd";
$param["docId"] = "32";
$param["DocType"] = "tscDoc";

$result = $client->InvokeIntoBaas($param);
var_dump($result);
?>

只有通过键值对数组的方式,WebService才能把传入的内容准确的识别。也就是说,WebService识别传入的参数不是通过参数的前后顺序,而是通过参数键值对中的键名。

3.2 参数内容以wsdl中的内容为准。

为什么上面要强调下wsdl呢?是因为我拿到WebService的接口文档时,接口文档里面写的一些内容无论我怎样试验都无法使用成功。最后发现,接口文档时间太久了,其中的某个字段名称已经更新。
而我是怎么知道的呢?
是通过看wsdl中<sequence>下的那些<element>知道的。
换句话说,再你向WebService中的方法传参的时候,参数键值对中的键名一定要和wsdl中的名字一一对应,差一个字母也不可以。

必须要提的
就通过这一点,我很肯定的指出了文档的错误,一个现学现用的新手给WebService开发人员还上了一课。是不是很厉害!哈哈哈!

4. 几个重要的函数

echo("打印暴露的方法:");
var_dump($client->__getFunctions());
print("<br/>");
echo("打印对应方法的参数和参数类型:");
var_dump($client->__getTypes());
print("<br/>");
echo("最后一次请求的头数据:");
var_dump($client->__getLastRequestHeaders());
echo "<br>";
echo("最后一次请求的数据:");
var_dump($client->__getLastRequest());
echo "<br>";
echo("最后一次返回的头数据:");
var_dump($client->__getLastResponseHeaders());
echo "<br>";
echo("最后一次返回的数据:");
var_dump($client->__getLastResponse());

PHP中使用SoapClient对象访问wsdl得到空object(stdClass),而__getLastResponse()方法却能获取XML返回结果的解决方案

1. 问题重现

<?php
$client = new \SoapClient('http://192.168.0.111:8989/dbservice/selectAll?wsdl');
$param = [];
$param['users'] = 'all';
$result =  $client->selectAllinspectOperation($param);
var_dump($result);
?>

运行后的返回结果如下,以下结果是全部返回内容,并没有隐藏什么:

object(stdClass)[2]
  public 'record' => 
    array (size=2)
      0 => 
        object(stdClass)[3]
      1 => 
        object(stdClass)[4]

2. 思考过程

  • 首先,运行结果没有报错。
  • 然后,selectAllinspectOperation()这个函数的目的是返回数据库中所有的结果。而数据库中目前只有两条数据,所以从返回结果来看,正好有个长度为2的数组。所以我认为查询是成功,且返回了正确的条目数。
  • object(stdClass)这个东东我比较陌生,查了下php文档,是个叫做基类的对象。具体是啥东东对本问题不是很重要,就不赘述了。当时我认为问题就是在object(stdClass)上,可能object(stdClass)把返回结果都封装隐藏起来了,所以看不到。
  • 于是乎又是一顿狂查,网上查到了一个函数get_object_vars()可以把object转为数组,试了一下,确实是把对象转为数组了,上面代码最后一句修改以下,运行结果如下:
    //var_dump(get_object_vars($result));运行结果
    array (size=1)
    'record' => 
    array (size=2)
      0 => 
        object(stdClass)[3]
      1 => 
        object(stdClass)[4]

    不过上面的结果只把最外层的object转为了数组,于是再深入以下,把里面的object(stdClass)也转为数组,同样是最后一行代码修改下,运行结果如下:

    //var_dump(get_object_vars(get_object_vars($result)["record"][0]));运行结果
    array (size=0)
    empty
  • 这下子又晕了,得到的是空值。于是又是一顿狂查,查到了几个SoapClient中的函数,可以打印SoapClient调用过程中的各种信息,于是把每个函数都写下来看看能返回什么结果:
    echo("打印暴露的方法:");
    var_dump($client->__getFunctions());
    print("<br/>");
    echo("打印对应方法的参数和参数类型:");
    var_dump($client->__getTypes());
    print("<br/>");
    echo("最后一次请求的头数据:");
    var_dump($client->__getLastRequestHeaders());
    echo "<br>";
    echo("最后一次请求的数据:");
    var_dump($client->__getLastRequest());
    echo "<br>";
    echo("最后一次返回的头数据:");
    var_dump($client->__getLastResponseHeaders());
    echo "<br>";
    echo("最后一次返回的数据:");
    var_dump($client->__getLastResponse());

    最重要的就是最后一个__getLastResponse()函数,发现所有期望的查询结果实际都返回回来了:
    file

  • 这就奇怪了不是吗?明明已经返回了查询结果,为什么之前获取不到呢?于是我放弃了这个接口,而是测试接口文档中其它的接口,却惊喜的发现其它接口是可以正常返回数据的,虽然返回的也是object(stdClass)类型数据,但是object(stdClass)下面是有具体的数值的。于是我开始怀疑,是不是给的接口有问题?
  • 于是又是一轮查询和思考。说实话,这方面的文档真的很少。经过国内、国外各种网站的查询和信息汇总。最终确定,确实是接口有问题。

3. 问题说明。

基本可以肯定的是,如果可以通过__getLastResponse()看到数据,而打印的数据是空object(stdClass),这个不是使用WebService的问题,而是服务器端WebService的返回值不符合相关规范,导致SoapClient无法对返回结果正常解析导致的。
所以,不要再症结这个问题了,除非你能修改WebService的内容,否则常规手段解决不了这个问题。

4. 解决方案。

之前也说过了,可以通过__getLastResponse()获取返回的内容。反正已经有了结果了,直接处理结果字符串不就好了吗。
所以解决方案很简单,直接使用PHP的XML处理函数,或者直接写个正则表达式获取里面的结果就好了!

5. 总结

我把以上这些情况反馈给了接口提供方,那边也最终证实,确实是他们返回的数据格式有瑕疵。
重要的事情说三遍,遇到这种情况:
不是你的问题!
不是你的问题!
不是你的问题!
是WebService的问题!
是WebService的问题!
是WebService的问题!

PHP正则表达式函数preg_match_all()用法

1. preg_match_all()

preg_match_all("/匹配模式/",$字符串,$匹配结果);

1.1 $匹配结果

匹配出来的结果存储在多维数组中,从多维数组中获取匹配结果的方式如下:

1.1.1 如果不含()子表达式

如果匹配模式中不含()子表达式的话,所有匹配结果都存储在返回结果第一个维度的第一个元素中,即:

$匹配结果[0][0];
$匹配结果[0][1];
$匹配结果[0][2];
1.1.2 如果含()子表达式

如果匹配模式不含()子表达式的话,整体匹配结果存储在返回结果第一个维度的第一个元素中;子表达式的匹配结果则顺延至返回结果的第一个维度的第二个、第三个...元素中,即:

//整体匹配结果
$匹配结果[0][0];
$匹配结果[0][1];
$匹配结果[0][2];
//第一个子表达式匹配结果
$匹配结果[1][0];
$匹配结果[1][1];
$匹配结果[1][2];
//第二个子表达式匹配结果
$匹配结果[2][0];
$匹配结果[2][1];
$匹配结果[2][2];

1.2 $字符串

$字符串必须在一行中。如果不在一行,则再匹配是需要考虑换行符\r\n的问题。

2. 任务描述

通过PHP的SOAP获得了一个XML的字符串,需要对这个XML字符串中的内容进行提取。XML字符串内容如下:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"/>
    <soap:Body>
        <dbs:resultset xmlns:dbs="http://www.royotech.com/">
        <record>
            <float_id>95551e1d7bf9430d80361a0d21d13cd3</float_id>
            <float_name>张三</float_name>
            <float_sex>男</float_sex>
            <float_native>北京市东城区</float_native>
            <float_card>110102199008080808</float_card>
            <float_address>福建省莆田区</float_address>
            <float_unit>福州市美术设计制作有限公司</float_unit>
            <float_unit_adress>福建省莆田区</float_unit_adress>
            <float_job>销售</float_job>
            <float_states>0</float_states>
        </record>
        <record>
            <float_id>be7b91fed02641108ddb612bf15de26c</float_id>
            <float_name>李四</float_name>
            <float_sex>男</float_sex>
            <float_native>北京市东城区</float_native>
            <float_card>110103199106060606</float_card>
            <float_address>新乡市</float_address>
            <float_unit>新乡市电子技术有限公司</float_unit>
            <float_unit_adress>新乡市</float_unit_adress>
            <float_job>IT开发</float_job>
            <float_states>0</float_states>
        </record>
        </dbs:resultset>
    </soap:Body>
</soap:Envelope>

3. 代码

<?php
    $xml = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"/><soap:Body><dbs:resultset xmlns:dbs="http://dbservices.tongtech.com/"><record><float_id>95551e1d7bf9430d80361a0d21d13cd3</float_id><float_name>庄正卿</float_name><float_sex>男</float_sex><float_native>北京市东城区</float_native><float_card>110101199003077950</float_card><float_address>福建省莆田区</float_address><float_unit>福州市益聚美术设计制作有限公司</float_unit><float_unit_adress>福建省莆田区</float_unit_adress><float_job>销售</float_job><float_states>0</float_states></record><record><float_id>be7b91fed02641108ddb612bf15de26c</float_id><float_name>逯俊郎</float_name><float_sex>男</float_sex><float_native>北京市东城区</float_native><float_card>110101199003078793</float_card><float_address>新乡市</float_address><float_unit>新乡市欣丰电子技术有限公司</float_unit><float_unit_adress>新乡市</float_unit_adress><float_job>IT开发</float_job><float_states>0</float_states></record></dbs:resultset></soap:Body></soap:Envelope>';

    $records = null;
    preg_match_all("/\<record\>.*?\<\/record\>/",$xml,$records);

    $tags = [   "inspect_id",
                "report_time",
                "recorder_name",
                "report_name",
                "report_dex",
                "report_card",
                "report_phone",
                "report_dec",
                "result",
                "inspect_states",
            ];

    foreach ($records as $record){
        foreach ($tags as $tag){
            echo $tag . " " . getTagInfo($tag,$record[0]) . "<br/>";
        }
    }

    function getTagInfo($tagName,$str){
        $arr_tag = "";
        preg_match_all("/\<".$tagName."\>(.*?)\<\/".$tagName."\>/",$str,$arr_tag);
        return $arr_tag[1][0];
    }
?>

ThinkPHP5.0增删改操作方法

1. 增加

1.1 单条增加

增加的数据存在数组里,然后调用table的insert方法。

    public function addOneNews(){
        $data = [
            "id" => 35,
            "content" => "新增加的内容",
            "author" => "moonlightgate",
            "price" => 888
        ];
        $result = DB::table("ksdt_cms_addonnews")->insert($data);
    }

操作成功后,返回值$result是影响的行数。
如果使用insertGetId($data)方法,则返回新增后的数据id。

1.2 批量增加

将要增加的数据放置在数组里面,然后使用table的insertAll方法。

public function addMoreNews(){
        $data = [
            [
                "id" => 36,
                "content" => "新增加的内容1",
                "author" => "moonlightgate",
                "price" => 888
            ],
            [
                "id" => 37,
                "content" => "新增加的内容2",
                "author" => "moonlightgate",
                "price" => 999
            ]
        ];
        DB::table("ksdt_cms_addonnews")->insertAll($data);
    }

2. 修改

2.1 where修改

  public function updateInfo(){
        $data = [
            "author" => "ROYOTECH"
        ];
        Db::table("ksdt_cms_addonnews")->where("id","36")->update($data);
    }

2.2 主键修改

如果修改的数组中包含主键,则无需再使用where。

public function updateInfo(){
        $data = [
            "id" => 36,
            "author" => "ROYOTECH"
        ];
        Db::table("ksdt_cms_addonnews")->update($data);
    }

2.3 运算修改

可以在update时使用inc,dec方法进行数值增减运算。

public function updateInfo(){
        Db::table("ksdt_cms_addonnews")->inc("price",2)->where("id",36)->update();
    }

2.4 MySQL函数修改

可以通过exp方法直接调用MySQL函数

    public function updateInfo(){
        Db::table("ksdt_cms_addonnews")->exp("author","LOWER(author)")->where("id",36)->update();
    }

2.5 raw方法修改

将运算过程通过raw函数写在数组里面,然后update

    public function updateInfo(){
        $data = [
            "id" => 36,
            "price" => Db::raw('price - 3'),
            "author" => Db::raw('upper(author)')
        ];
        Db::table("ksdt_cms_addonnews")->update($data);
    }

3. 删除

3.1 单条删除

    public function deleteInfo(){
        Db::table("ksdt_cms_addonnews")->delete(36);
    }

3.2 delete多条删除

    public function deleteInfo(){
        Db::table("ksdt_cms_addonnews")->delete([36,37]);
    }

3.3 where多条删除

    public function deleteInfo(){
        $where['id'] = array('in','1,2,3,4,5');
        Db::table("ksdt_cms_addonnews")->where($where)->delete();
    }

ThinkPHP5.0随学随记

1. 查看最后运行的SQL语句

return DB::getLastSql();

2. 链式查询

低效率方式:

$data1 = Db::name("user")->where("id",27)->select();
$data2 = Db::name("user")-select();

高效率方式:

$data = Db::name("user");
$result1 = $data->where("id",27)->select();
$result2 = $data->select();

然而运行结果发现,在高效模式下,$result2和$result1的返回结果是一样的。如果要去掉$result1的干扰因素,需要这样:

$data = Db::name("user");
$result1 = $data->where("id",27)->order("id","desc")->select();
$result2 = $data->removeOption("where")->removeOption("order")->select();

可以看出,语句变复杂了。
这个问题上需要自己去权衡效率和语句简洁度之间的问题。

ThinkPHP5.0数据库查询入门及常用的几种数据库查询方式

1. 官方文档

本文内容参考Thinkphp5.0官方文档:https://www.kancloud.cn/manual/thinkphp5
为了你后期方便,将官方文档下载版本也一并奉上:
PDF版本:
ThinkPHP5.0完全开发手册-03091011
EPUB版本:
ThinkPHP5.0完全开发手册-03091011-EPUB
MOBI版本:
ThinkPHP5.0完全开发手册-03091011-MOBI

2. 配置数据库参数。

可以在以下文件中配置数据库链接信息:

application/database.php

3. 支持多种查询方式。

ThinkPHP5.0在开发的时候就考虑到了以下几个问题,所以以下查询都是支持的:

3.1 连接多种数据库

ThinkPHP5.0默认支持MySQL,SQLServer等数据库的链接,Oracle经过简单的配置也是支持的。

3.2 支持多种数据库

即便在配置信息中写定了某个数据库的链接信息,在实际查询数据库的时候,仍然可以单独制定数据库链接信息。所以,配置信息中的数据库可以写操作最频繁的数据库信息。其它数据库可以在连接时单独配置。

3.3 支持分布式数据库

ThinkPHP也支持主从类型的分布式数据库操作。

4. 常用的查询方式。

4.1 Controller通过DB::table

table的参数需要添加的是完整的数据库表名称。

$result = DB::table("ksdt_addonnews")->select();

4.2 Controller通过DB::name

name的参数需要添加的是无前缀的数据库表名称。

$result = DB::name("addonnews")->select();

4.3 通过Controller调用Model

4.3.1 建立model

在controller同级目录下新建model文件夹,并在该文件夹中新建数据表对应的model文件。

model的文件名和类名需要统一,会自动和数据表产生数据映射,model文件名和类的命名规则是除去表前缀的数据表名称,采用驼峰法命名,并且首字母大写,例如:

模型名 约定对应数据表(假设数据库的前缀定义是 think_)
User think_user
UserType think_user_type
4.3.2 撰写model代码
//数据库真实表名为:ksdt_cms_addonnews
//model文件名为:CmsAddonnews.php
<?php
namespace app\menus\model;
use think\Model;

class CmsAddonnews extends Model{

}
4.3.3 在controller中调用model
<?php
namespace app\menus\controller;
use app\menus\model\CmsAddonnews;
use think\Db;

class NewsList {
    public function doDbByModel(){
        $result = CmsAddonnews::select();
        var_dump($result);
    }
}

ThinkPHP框架URL结构详解

1. URL格式

http://网站域名/index.php/模块/控制器/方法/参数/参数值

其中index.php是可以省略的,因为不写index.php也会默认执行到index.php上。

2. 通过新建一个传参页面详解URL

2.1 功能说明

比如我们建立一个点菜模块叫menus,menus下面新建一个早餐的控制器叫Breakfast,Breakfast下面再新建一个打印菜单的方法叫showMenu(),并可以给该方法传参。

2.2 文件和目录结构

file

  • 需要注意的是,控制器文件Breakfast.php的文件名首字母必须大写。
  • 如果采用驼峰命名法比如BreakFast.php命名,那么访问该控制器的时候,URL路径上需要在驼峰字母中间添加下划线Break_Fast

    2.3 Breakfast.php代码

    
    <?php
    namespace app\menus\controller;

class Breakfast
{
public function showMenu($priceRange = "1000")
{
return "请提供价格小于".$priceRange."元的菜单!";
}
}


#### 2.4 代码,文件结构、URL的对应图
![file](http://www.moonlightgate.com/wp-content/uploads/2020/03/image-1583897364247.png)

Docker上部署一个PHP+MySQL网站的方法

1. 前言

1.1 Docker适合什么类型的项目?

Docker存在的意义是解决项目移植时兼容的问题。
好吧,那么Docker是否对于任何项目都合适呢?
就我目前的体验而言,使用Docker做负载均衡是比较合适的,用Docker装载静态的,较小的项目也是合适的。
如果是想用Docker装载一个大型项目,个人感觉不太合适。这个理论有待后期使用的时候再慢慢探索。

1.2 PHP+MySQL项目部署在Docker的几种思路。

1.2.1 使用tomsik68/xampp

这也是本文要重点探讨的,详情见后面内容。

1.2.2 使用httpd然后再自行安装mysql和php

我会单独写一篇文章记录这个方法。

1.2.3 使用pch18/baota

这种方法是将宝塔软件放在Docker里面。我也会单独写一篇文章记录这个方法。但是这种方法我认为有个致命的问题,就是pch18/baota镜像文件太大了,要5G。你没看错,要5G。所以这种方案的可行性非常值得商榷,因为这很明显违背了Docker存在的意义。

2. 安装部署tomsik68/xampp

下面的内容参考:https://hub.docker.com/r/tomsik68/xampp

2.1 拉取镜像

docker pull tomsik68/xampp

2.2 为宿主机创建mysql数据卷

docker volume create mysql

上述命令的mysql名称任意,为了方便识别用的mysql。

2.3 运行容器

宿主机bind mount网页文件的位置:/opt/www
宿主机映射端口地址:41062
mysql目录不能使用文件夹bind mounts的形式,以下方法执行后会因为权限问题报错。

docker run --name myXampp -p 41061:22 -p 41062:80 -it -v /opt/www:/www -v /opt/mysql:/opt/lampp/var/mysql tomsik68/xampp

必须使用下方数据卷的方法:

docker run --name myXampp -p 41061:22 -p 41062:80 -it -v /opt/www:/www -v mysql:/opt/lampp/var/mysql tomsik68/xampp

2.4 测试运行

在/opt/www 中新建index.php文件,写入如下代码:

<?php
echo "hello world";
echo 1+3;
?>

然后在浏览器中打开地址:http://宿主机IP地址或域名:41062/www/index.php
如果看到hello world4,证明容器运行成功了!
如果运行不成功,可能是因为宿主机防火墙问题,或者关闭防火墙,或者放行41062端口即可。

2.5 进入容器查看

如果想要查看容器内部情况,使用以下命令:

docker exec -it myXampp bash

3. 导入一个PHP+MYSQL网站

我们就以安装一个“帝国CMS”为例演示网站导入过程。

3.1 下载解压安装包。

进入/opt/www目录后下载安装包

wget http://ecms.phome.net/downcenter/empirecms/ecms75/download/EmpireCMS_7.5_SC_UTF8.zip

解压

unzip EmpireCMS_7.5_SC_UTF8.zip

为了方便我把解压后的upload文件夹重名名为了ecms

mv upload ecms

3.2 更改文件夹权限

如果不更改,安装时会提示权限不足。

chmod 777 -R ecms

3.3 完成安装

http://宿主机IP地址或域名:41062/www/ecms/e/install/

按步骤提示完成安装即可。
注:安装时使用xampp默认的mysql用户名:root,密码是空。

4. 数据持久化问题

A. 对于一个web站点,有两部分数据需要持久化:数据库+网页代码。
B. 在tomsik68/xampp给出的官方文档中,run命令是这样写的:

docker run --name myXampp -p 41061:22 -p 41062:80 -d -v /opt/www:/www tomsik68/xampp

上面的命令只实现了网页文件的持久化,没有实现数据库持久化。这样的问题就是每当服务器重启的时候,数据库数据就会丢失。
C. 为了解决数据库持久化的问题,在2.3中我将命令修改为以下形式:

docker run --name myXampp -p 41061:22 -p 41062:80 -it -v /opt/www:/www -v mysql:/opt/lampp/var/mysql tomsik68/xampp

这样,即便服务器重启,只需要使用以下命令就可以重新恢复网站:

docker start myXampp

Xshell6 Portable提示“要继续使用此程序,您必须应用最新的更新或使用新版本

1. 前言

Xshell6是收费软件,同时提供免费的体验版本Xshell6 Portable。
然而,在使用Xshell6 Portable一段时间后,会突然间弹出这样的框:
file
其实意思很简单,就是告诉你试用期到了。

2. 推荐解决方案

如果你觉得Xshell6好用,就尽快去官网购买正版软件吧。这是最明智的做法。

3. 应急解决方案

很多时候你可能不方便马上就购买,需要应急。这时有两个方法:

3.1 调整系统时间

只需将电脑的系统时间往前调整一段时间,比如1年。就可以正常打开网站了。

3.2 修改nslicense.dll文件

3.2.1 为编辑器安装二进制插件

为二进制编辑器(UltraEdit、notepad++)安装HEX-Editor。

3.2.2 用编辑器打开nslicense.dll文件

nslicense.dll位置如下:

安装目录\Xshell6Portable\App\Xshell

文件打开后,需要切换Hex视图:
file

3.2.3 编辑nslicense.dll文件
  • 搜索“7F 0C 81 F9 80 33 E1 01 0F 86 81”
  • 修改“86”为“83”
  • 保存文件
  • 重新打开Xshell发现已经可以使用

注:如果是xshell 5,搜索“7F 0C 81 F9 80 33 E1 01 0F 86 80”然后修改“86”为“83”。