三步实现低代码框架 - x3d - 博客园

mikel阅读(720)

来源: 三步实现低代码框架 – x3d – 博客园

 


突然想到这么一个标题党的事情,试试看。注:仅基于PHP做简单梳理,未完成成品。

先想清楚核心原理,然后分别从后端、前端设计实现。

核心原理

低代码,如果简单理解为针对常规应用的CRUD场景,以一种DSL语言的形式,实现系统的开发。这种形式,减少了程序员的重复劳动,甚至可以让不太懂程序开发的人也能完成系统的开发-这也许正是“低”的含义。实则,我们知道多数人的认知是模型驱动的开发思想。

要达到后者的目的,首先得定义一套DSL语言,以便一般用户容易掌握,从而通过对CRUD这类功能所需信息的定义来实现系统的开发。

软件工程中,传统过程是要先设计,再实现;通过低代码的DSL语言,实际上将设计和实现两个动作合二为一了,设计即实现。

软件设计方法分结构化设计和面向对象设计,我们所熟知的画ER图就是结构化设计的经典步骤。数据实体、属性、关系,作为我们对现实世界事物静态状态下的认知的映射,是最基础的分析和设计工作。因此,我们的DSL语言中,首先要支持实体的定义。

这里,我们可以考虑使用PlantUML中的实体关系图语言作为我们的DSL语言,这样有一个很大的好处,实体图可以渲染成图形格式便于评审交流。

不过稍微有点尴尬的是,目前PlantUML还没有原生的PHP解析引擎,需要封装其官方的Jar包调用执行解析和转换,才能最终生成PHP版的实体类代码。https://github.com/mk-conn/plant2code

后端实现

路由

选一MVC框架,如ThinkPHP v6,基于其灵活的路由机制,实现按实体的CRUD界面和API请求入口调度。

在路由定义文件(config/route.php)中加上:

use think\facade\Route;

// 主界面
Route::get('lc/:entity$', 'EntityCRUD/index');
// API
Route::rule('lc-api/:entity/:action', 'EntityCRUD/:action');

CRUD

基于 think-orm 的特性,可以很便捷的实现相应功能。下面做个示范:

https://www.kancloud.cn/manual/think-orm/1257998

<?php

namespace app\controller;

use app\BaseController;
use think\facade\Db;

class EntityCRUD extends BaseController
{
    /**
     * 构造 CRUD UI
     * @param $entity
     */
    public function index($entity)
    {
        echo $entity . ' Entity/index';
    }

    /**
     * 查询API
     * @param $entity
     */
    public function retrieve($entity)
    {
        echo $entity . ' Entity/retrieve';
    }

    /**
     * 增加API
     * @param $entity
     */
    public function create($entity)
    {
        $data = input('post.');

        $id = Db::name($entity)->insertGetId($data);

        echo $entity . ' Entity/create:' . $id;
    }

    /**
     * 修改API
     * @param $entity
     * @param $id
     */
    public function update($entity, $id)
    {
        $data = input('post.');
        Db::name($entity)->where('id' , $id)->update($data);
        echo $entity . ' Entity/update ' . $id;
    }

    /**
     * 删除API
     * @param $entity
     * @param $id
     */
    public function delete($entity, $id)
    {
        Db::table($entity)->delete($id);
        echo $entity . ' Entity/delete ' . $id;
    }
}

实体定义

基于plantuml 的语法,如果数据库使用mongodb,则直接用plantuml的实体关系图特性即可完成实体类的完整定义,都不用考虑与数据库层字段类型的映射问题;如果基于传统的关系型数据库如mySQL,则需要进一步实现字段类型的映射。

app/definition/entity/company.puml

@startuml
entity "Company" as e01 {
  *id: int <<generated>>
  *name: string
 *phone1: string
  --
  phone2: string
  fax: string
  address1: string
  address2: string
  city: string
  state: string
  zip: string
  primary_url: string
  owner: int
  *type: int
  email: string
  description: text
}

@enduml

或者基于plantuml也能处理的JSON或YAML格式:


@startyaml
name: account_receivable_invoice
label: 收票
icon: account
enable_api: true
enable_files: true
fields:
  name:
    label: 标题
    type: text
    required: true
  bill_id:
    label: 付款单ID
    omit: true
    hidden: true
    type: text
  amount:
    label: 发票总金额
    type: number
    required: true
  invoice_number:
    label: 发票张数
    type: number
    required: true
  payable_id:
    label: 应付记录
    type: lookup
    reference_to: account_receivable
    relatedList: true
    required: true
  contract_id:
    label: 合同
    type: master_detail
    reference_to: contracts
    required: true
  owner:
    label: 上传人
    omit: false
    readonly: true
    type: lookup
    reference_to: users
  company_id:
    omit: false
    hidden: false
    label: 我方单位
list_views:
  all:
    label: 所有
    columns:
      - name
      - amount
      - invoice_number
      - owner
      - created
permission_set:
  user:
    allowCreate: false
    allowDelete: false
    allowEdit: false
    allowRead: true
    modifyAllRecords: false
    viewAllRecords: false
    modifyCompanyRecords: false
    viewCompanyRecords: true
  admin:
    allowCreate: true
    allowDelete: true
    allowEdit: true
    allowRead: true
    modifyAllRecords: true
    viewAllRecords: true
@endyaml

注:取自 steedos 项目中代码,最终语法与前端框架统一或不统一均可,仅演示思路。

对应的SQL


CREATE TABLE `company` (
  `id` INT(10) NOT NULL auto_increment,
  `module` INT(10) NOT NULL default '0',
  `name` varchar(100) default '',
  `phone1` varchar(30) default '',
  `phone2` varchar(30) default '',
  `fax` varchar(30) default '',
  `address1` varchar(50) default '',
  `address2` varchar(50) default '',
  `city` varchar(30) default '',
  `state` varchar(30) default '',
  `zip` varchar(11) default '',
  `primary_url` varchar(255) default '',
  `owner` int(11) NOT NULL default '0',
  `description` text,
  `type` int(3) NOT NULL DEFAULT '0',
  `email` varchar(255),
  `custom` LONGTEXT,
  PRIMARY KEY (`id`),
	KEY `idx_cpy1` (`owner`)
);

以前一直眼馋的是yii框架中的db相关工具,比较专业又易懂,如果能将其抽取出来独立使用就好了。

DB初始化

实现实体定义中字段类型与相应数据库引擎的字段映射关系解析和处理后,再借助于框架的数据库迁移工具,可以比较容易实现DB Shcema的初始化,包括Schema升级.

https://www.kancloud.cn/manual/thinkphp6_0/1118028

UI 渲染

选一前端低代码框架如百度的 amis,其中自带CRUD专用组件,可以很简单的方式实现。

https://baidu.gitee.io/amis/zh-CN/components/crud

按照其CRUD所需返回的相应格式在后端组装数据格式,返回传递给amis即可。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8" />
    <title>amis demo</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta
            name="viewport"
            content="width=device-width, initial-scale=1, maximum-scale=1"
    />
    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
    <?php
    $base    = request()->root();
    $root    = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base;
    if ('' != $root) {
        $root = '/' . ltrim($root, '/');
    }
    ?>
    <link rel="stylesheet" href="<?php echo $root ?>/static/amis-sdk-v1.1.6/sdk.css" />
    <link rel="stylesheet" href="<?php echo $root ?>/static/amis-sdk-v1.1.6/helper.css" />
    <!-- 从 1.1.0 开始 sdk.css 将不支持 IE 11,如果要支持 IE11 请引用这个 css,并把前面那个删了 -->
    <!-- <link rel="stylesheet" href="sdk-ie11.css" /> -->
    <!-- 不过 amis 开发团队几乎没测试过 IE 11 下的效果,所以可能有细节功能用不了,如果发现请报 issue -->
    <style>
        html,
        body,
        .app-wrapper {
            position: relative;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
<div id="root" class="app-wrapper"></div>
<script src="<?php echo $root ?>/static/amis-sdk-v1.1.6/sdk.js"></script>
<script type="text/javascript">
    (function () {
        let amis = amisRequire('amis/embed');
        // 通过替换下面这个配置来生成不同页面
        let amisJSON = {
            "type": "page",
            "body": [
                {
                    "label": "新增",
                    "type": "button",
                    "actionType": "dialog",
                    "level": "primary",
                    "className": "m-b-sm",
                    "dialog": {
                        "title": "新增表单",
                        "body": {
                            "type": "form",
                            "api": "post:https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample",
                            "controls": [
                                {
                                    "type": "text",
                                    "name": "engine",
                                    "label": "Engine"
                                },
                                {
                                    "type": "text",
                                    "name": "browser",
                                    "label": "Browser"
                                }
                            ]
                        }
                    }
                }, {
                "type": "crud",
                "api": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample",
                "syncLocation": false,
                "columns": [
                    {
                        "name": "id",
                        "label": "ID"
                    },
                    {
                        "name": "engine",
                        "label": "Rendering engine"
                    },
                    {
                        "name": "browser",
                        "label": "Browser"
                    },
                    {
                        "name": "platform",
                        "label": "Platform(s)"
                    },
                    {
                        "name": "version",
                        "label": "Engine version"
                    },
                    
                    {
                        "type": "operation",
                        "label": "操作",
                        "buttons": [
                            {
                                "label": "修改",
                                "type": "button",
                                "actionType": "drawer",
                                "drawer": {
                                    "title": "修改表单",
                                    "body": {
                                        "type": "form",
                                        "initApi": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample/${id}",
                                        "api": "post:https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample/${id}",
                                        "controls": [
                                            {
                                                "type": "text",
                                                "name": "engine",
                                                "label": "Engine"
                                            },
                                            {
                                                "type": "text",
                                                "name": "browser",
                                                "label": "Browser"
                                            }
                                        ]
                                    }
                                }
                            },
                            {
                                "label": "删除",
                                "type": "button",
                                "actionType": "ajax",
                                "level": "danger",
                                "confirmText": "确认要删除?",
                                "api": "delete:https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample/${id}"
                            }
                        ]
                    }
                ]
            },
            ]
        };
        let amisScoped = amis.embed('#root', amisJSON);
    })();
</script>
</body>
</html>

注:未完成动态拼接。

进阶

权限、安全、代码缓存、细节完善等。

搭建自己的低代码平台 - 菜菜聊架构 - 博客园

mikel阅读(1190)

来源: 搭建自己的低代码平台 – 菜菜聊架构 – 博客园

2021 开年“低代码”成了热门话题,各大云厂商都在加码。作为普通企业的我们是否有也可以深度定制一套自己的“低代码”平台呢?

2|0二、云厂商的低代码平台

阿里推出了易搭,通过简单的拖拽、配置,即可完成业务应用的搭建。旨在为广大中小企业提供一套低成本的企业应用搭建解决方案。应用无缝植入钉钉企业工作台,随时随地、高效协同。

基于 magic-api 搭建自己的低代码平台

腾讯则是推出了微搭,通过行业化模板、拖放式组件和可视化配置快速构建多端应用(小程序、H5 应用、Web 应用等),打通了小程序、云函数。

基于 magic-api 搭建自己的低代码平台

3|0三、搭建我们自己的低代码平台?!

回到前言中的问题,我们是否可以基于开源项目来快速搭建我们自己的低代码平台呢?答案是肯定的,目前已经有很多非常不错的开源项目,apijson、dataway 还有后面我要重点介绍的 magic-api 都是非常不错的低代码开源项目。下面大家请跟着我一起来看看今天我要推荐的三个低代码开源项目:百度 amis、h5-Dooring 和 magic-api。

4|03.1 百度 amis(前端)

百度 amis 是一套前端低代码框架,通过 JSON 配置就能生成各种后台页面,极大减少开发成本,甚至可以不需要了解前端。

基于 magic-api 搭建自己的低代码平台

5|03.2 h5-Dooring(前端)

h5-Dooring,让 H5 制作像搭积木一样简单, 轻松搭建 H5 页面, H5 网站, PC 端网站, 可视化设计。

H5 页面拖拽生成:

基于 magic-api 搭建自己的低代码平台

新建数字大屏:

基于 magic-api 搭建自己的低代码平台

数字大屏效果:

基于 magic-api 搭建自己的低代码平台

更多请查看官网 http://h5.dooring.cn

6|03.3 magic-api(后端)

magic-api 是一个基于 Java 的接口快速开发框架,编写接口将通过 magic-api 提供的 UI 界面完成,自动映射为 HTTP 接口,无需定义 Controller、Service、Dao、Mapper、XML、VO 等 Java 对象即可完成常见的 HTTP API 接口开发。

在线开发调试 UI:

基于 magic-api 搭建自己的低代码平台

7|0四、magic-api 搭建

自 magic-api 在开源中国开源,笔者一直在关注此项目。magic-api 搭建比较简单,跟着官方仓库快速开始即可。

8|04.1 加入依赖

<!– 以 spring-boot-starter 的方式引用 –> <dependency> <groupId>org.ssssssss</groupId> <artifactId>magic-api-spring-boot-starter</artifactId> <version>1.3.9</version> </dependency>

9|04.2 添加配置

server.port=9999 #配置 web 页面入口 magic-api.web=/magic/web #配置文件存储位置。当以 classpath 开头时,为只读模式 magic-api.resource.location=/data/magic-api

10|04.3 效果

基于 magic-api 搭建自己的低代码平台

11|04.4 添加接口

基于 magic-api 搭建自己的低代码平台

点击执行就可以看到效果,这里不做展示,magic-api 官方做了大量的各种各样的功能演示,大家可以去体验体验。地址:
http://140.143.210.90:9999/magic/web/index.html

基于 magic-api 搭建自己的低代码平台

12|04.5 magic-api 核心之 magic-script

magic-api 底层使用的是作者(小东)自研的 magic-script 来执行脚本,他是一个语法类似 js 的 JVM 脚本语言。作者开发很多好用的功能, 其中的 linq 功能就是非常的一个。

示例:

var list = [{ sex : 0, name : ‘小明’ },{ sex : 1, name : ‘小花’ }] return select * from list t where t.sex = 0

结果:

{ “sex”: 0, “name”: “小明” }

13|0五、结合 mica 使用

14|05.1 加入 mica-bom 依赖

<dependencyManagement> <dependencies> <dependency> <groupId>net.dreamlu</groupId> <artifactId>mica-bom</artifactId> <version>${mica.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

15|05.2 mica-logging 无缝接入

mica-logging 的接入比较简单加入依赖即可:

基于 magic-api 搭建自己的低代码平台

注意:更多配置,如 json 日志和 logstash 日志收集请查看 mica 文档。

16|05.3 mica-captcha 验证码

base64 验证码:

基于 magic-api 搭建自己的低代码平台

17|05.4 mica-ip2region ip 转位置信息

基于 magic-api 搭建自己的低代码平台

示例项目源码:
https://gitee.com/596392912/magic-api-example

从 mica 的接入可以看出,magic-api 的扩展性非常好,更多的自定义配置和扩展我们可以查看 官方文档 :

基于 magic-api 搭建自己的低代码平台

18|0六、低代码开源

19|0前端:

amis(百度前端低代码框架):
https://github.com/baidu/amis

Sortable: https://github.com/SortableJS/Sortable

码良:
https://gitee.com/ymm-tech/gods-pen

h5-Dooring: https://github.com/MrXujiang/h5-Dooring

20|0后端:

apijson: https://github.com/Tencent/APIJSON

dataway: https://gitee.com/zycgit/hasor

magic-api: https://gitee.com/ssssssss-team/magic-api

21|0七、总结

基于 magic-api 等开源项目我们也可以搭建自己的低代码平台。当然开发这个低代码平台我们还是需要些工作量,主要是打通 web ui 和后端接口的 api。笔者曾经也经历过一个电商项目,很多活动都是周五下午接到需求,周一就必须要上线。借助 magic-api 这类低代码工具我们可以快速完成这类需求。最后,希望这篇文章对大家有所启发。

【学习路径】程序开发学习路径图

mikel阅读(1142)

【学习路径】PHP学习路径图

【学习路径】编程语言大体总述图

【学习路径】大数据工程师必备技能图
【学习路径】移动app测试工程师技能总图
【学习路径】嵌入式开发必会技能总图

  
【学习路径】运维开发从入门到精通技能图

.NET C#杂谈(1):变体 - 协变、逆变与不变 - HiroMuraki - 博客园

mikel阅读(571)

来源: .NET C#杂谈(1):变体 – 协变、逆变与不变 – HiroMuraki – 博客园

0. 文章目的:

介绍变体的概念,并介绍其对C#的意义

 

1. 阅读基础

了解C#进阶语言功能的使用(尤其是泛型、委托、接口)

 

2. 从示例入手,理解变体

变体这一概念用于描述存在继承关系的类型间的转化,这一概念并非只适用于C#,在许多其他的OOP语言中也都有变体概念。变体一共有三种:协变、逆变与不变。其中协变与逆变这两个词来自数学领域,但是其含义和数学中的含义几乎没有关系(就像编程语言的反射和光的反射之间的关系)。从字面上来看这三种变体的名字多少有点唬人,但其实际意思并不难理解。广泛来说,三种变体的意思如下:

  • 协变(Covariance):允许使用派生程度更大的类型
  • 逆变(Contravariance):允许使用派生程度更小的类型
  • 不变(Invariance):只允许目标类型

或者换一种更具体的说法:

  • 协变(Covariance):若类型A为协变量,则需要使用类型A的地方可以使用A的某个子类类型。
  • 逆变(Contravariance):若类型A为逆变量,则需要使用类型A的地方可以使用A的某个基类类型。
  • 不变(Invariance):若类型A为不变量,则需要使用类型A的地方只能使用A类型。

(注意是‘协变/量’而不是‘协/变量’)

为了方便说明三者的含义,先定义两个类:

class Cat { }
class SuperCat : Cat { }

上述代码定义了一个Cat类,并从Cat类派生出一个SupreCat类,如无特殊说明,后文的所有代码都会假设这两个类存在。下面利用这两个类逐一说明三种变体的含义。

2.1 协变:在一个需要Cat的场合,可以使用SuperCat

例如,对于下列代码:

Cat cat = new SuperCat();

cat是一个引用Cat对象的变量,从类型安全的角度来说,它应该只能引用Cat对象,但是由于通常子类总是可以安全地转化为其某一基类,因此你也可以让其引用一个SuperCat对象。要实现这种用子类代替基类的操作就需要支持协变,由于OOP语言基本都支持子类向基类安全转化,所以协变在很多人看来是很十分自然的,也容易理解。

2.2 逆变:在一个需要SuperCat的场合,可以使用Cat

逆变有时也被称为抗变,你可能会觉得逆变的含义非常让人迷惑,因为通常来说基类是不能安全转化为其子类的,从类型安全的角度来看,这一概念应该似乎没有实际的应用场合,尤其是对于静态类型的语言。然而,考虑以下代码:

delegate void Action<T>();

void Feed(Cat cat)
{
    ...
}

Action<SuperCat> f = Feed;

Feed是一个‘参数为Cat对象的方法’,而f是一个引用‘参数为SuperCat对象的方法’的委托。从类型安全的角度来说,委托f应该只能引用参数为SuperCat对象的方法。然而如果你仔细思考上述代码,就会意识到既然委托f在调用时需要传入的是一个SuperCat对象,那么可以处理Cat类型的Feed方法显然也可以处理SuperCat(因为SuperCat可以安全转化为Cat),因此上面的代码从逻辑上来说是可以正常运行的。那么也就是说,本来需要SuperCat类型的地方(这里是委托的参数类型)现在实际给的却是Cat类型,要实现这种用基类代替子类的操作就需要逆变。

不过,结合上述,你会发现所谓逆变实际还是依靠‘子类可以向基类安全转化’这一原则,只是因为我们是从委托f的角度去考虑而已。

2.3 不变:在一个需要Cat的场合,只能使用Cat

相比逆变和协变,不变更容易理解:只接受指定类型,不接受其基类或者子类。比如如果Cat类型具有不变性,那么下述代码将无法通过编译:

Cat cat = new SuperCat(); // 错误,cat只能引用Cat类型

显然不变从表现上来说是理所当然与符合常识的,故本文主要阐述协变与抗变。

 

3. C#中的变体

3.1 C#中的变体

同大多数语言一样,C#同样遵循‘基类引用可以指向子类’这一基本原则,因此对C#来说协变是普遍存在的:

Feed(Cat cat)
{
    ...    
}

Cat cat = new SuperCat();           // 本来需要指向Cat对象的变量cat被指向了SuperCat对象,利用了协变性
SuperCat superCat = new SuperCat(); 
Feed(superCat);                     // 同理,Feed方法需要Cat对象但是传入的是SuperCat对象,利用了协变性

C#中的不变体现在值类型上,这是因为值类型都不允许继承与被继承,自然也不存在基类或子类的概念,也不存在类型间通过继承转化的情况。

C#中的逆变在一般情况下没有体现,因为将基类转化为派生类是不安全的,C#不支持这种操作。所以逆变对C#来说很多时候其实只是概念上的认识,真正让逆变对C#有意义的情况是使用泛型的场合,这在接下来就会提到。

从学习语言语法的角度来说,了解变体对学习C#的帮助其实不大,但如果想更进一步理解C#中泛型的设计原理,就有必要理解变体了。

3.2 泛型与变体

理解变体对理解C#的泛型设计原理有重要意义,C#中泛型的类型参数默认为不变量,但可以是outin关键字来指示类型为参数为协变量或者逆变量。简单来说,in关键字用于修饰输入参数的兼容性,out关键字用于修饰输出参数的兼容性。这一节会通过具体的泛型使用示例来解释变体概念对C#泛型的意义。

3.2.1 泛型委托

  (1)输入参数的兼容性:逆变

考虑下面的泛型委托声明:

delegate void Action<T>(T arg);

上述委托可以接受一个参数类型为T,返回类型为TReturn的委托。下面来定义一个方法:

void Feed(Cat cat)
{
    
}

Foo是一个接受一个Cat对象,并返回一个SuperCat对象的方法。因此,下面的代码是理所当然的:

Action<Cat> act = Feed;

然而,从逻辑上来讲,下面的代码也应该是合法的:

Action<SuperCat> act = Feed;

委托act接受的参数类型为SuperCat,也就是说当调用委托act的时候传入的将会是一个SuperCat对象,显然SuperCat对象可以安全地转换为Foo所需要的Cat对象,因此这一转变是安全的。我们以委托act的视角来看:本来act应该引用的是一个‘参数类型为SuperCat’的方法,然而我们却把一个‘参数类型为Cat的’Feed方法赋值给了它,但结合上面的分析我们知道这一赋值行为是安全的。也就是说,本来此时泛型委托Action<T>中泛型类型参数T需要的类型是SuperCat,但现在实际给的类型却是Cat:

(红色是方法参数类型)

Cat是SuperCat的基类,也就是说这时候泛型委托Action<T>的类型参数T这个位置上出现了逆变。尽管从逻辑上来说这是合理的,但是C#中泛型类型参数默认具有不变性,因此如果要使上述代码通过编译,还需要将泛型委托Func的类型参数T声明为逆变量,在C#中,可以通过在泛型类型参数前添加in关键字将泛型参数声明为逆变量:

delegate void Action<in T>(T arg);

  (2):输出参数的兼容性:协变  

另一方面,下面的代码从逻辑上说也应该是合法的:

delegate T Func<T>();

SuperCat GetSuperCat()
{
    ...
}

Func<Cat> func = GetSuperCat;

委托func被调用时需要返回一个Cat对象,而GetSuperCat返回的是一个SuperCat对象,这显然是满足func的要求的:

同样以委托func的视角来看,本来需要类型Cat的地方现在实际给的类型是SuperCat,也就是说,此时出现了协变。同样的,如果要使上述代码通过编译,应该需要将Func的类型参数T声明为协变量,可以在泛型参数前添加out关键字将泛型类型参数声明为协变量:

delegate T Func<out TReturn>();

3.2.2 泛型接口

(1)输出参数的兼容性:协变

假设现有以下用于表示集合的接口声明与实现该接口的泛型类:

interface ICollection<T>
{ 
}

class Collection<T> : ICollection<T>
{
}

根据上述定义,理所当然的,下面的语句是合法的:

ICollection<Cat> cats = new Collection<Cat>();

然而,从逻辑上讲,下面的语句也应该是合法的:

ICollection<Cat> cats = new Collection<SuperCat>();

原因如下:既然SuperCat是Cat的子类,那么Collection中的任意一个SuperCat对象都应该可以安全转化为Cat对象,那么SuperCat的集合也应该视为Cat的集合。从事实上讲,若对任何一个需要Cat对象集合的方法,即便传入的是一个SuperCat对象的集合也应该可以正常工作。同样以类型为ICollection<Cat>的接口变量cats的视角来看,ICollection<Cat>类型上本来应该为Cat类型的地方现在被SuperCat类型所替代:

SuperCat代替了Cat,也就是说出现了协变,那么如果要使上述代码通过编译,则需要将类型参数T声明为协变量:

interface ICollection<out T> 
{
}

C#中的IEnumerable接口就将其类型参数T声明为了协变量,因此下面的代码可以正常运行:

IEnumerable<Cat> cats = new List<SuperCat>();

(2)输入参数的兼容性:逆变 

接着再来考虑一个接口与实现类:

interface IHand<T>
{ 
    void Pet(T animal);
}

class Hand<T> : IHand<T> 
{
    void Pet(T animal) { ... }
}

下面的代码应该是合理的:

SuperCat cat = new SuperCat();        
IHand<SuperCat> hand = new Hand<Cat>(); 
hand.Pet(cat);

原因如下:实现IHand<Cat>接口的Hand<Cat>的Pet方法可以处理Cat类型,显然其应该也可以处理作为Cat子类的SuperCat。同样的,以类型为IHand<SuperCat>的接口变量hand来看,本来应该需要类型为SuperCat的地方现在实际却是Cat类型:

Cat替代了SuperCat,也就是说此时发生了逆变。同样的,如果要让上述代码通过编译,需要将IHand<>的类型参数T声明为逆变量:

interface IHand<in T>
{ 
    void Pet(T animal);
}

这样下述代码就可以通过编译:

IHand<SuperCat> hand = new Hand<Cat>();

3.2.3 泛型方法

与泛型委托和泛型接口不同的是,泛型方法不允许修改类型参数的变体类型,泛型方法的类型参数只能是不变量,因为让泛型方法的类型参数为变体没有意义。一方面,泛型方法的类型参数会在方法被调用时直接使用目标类型,因此不存在需要变体的情况:

void Pet<T>(T cat)
{
    ...
}

Pet(new Cat());      // 此时T为Cat
Pet(new SuperCat()); // 此时T为SuperCat

另一方面,你不能给一个方法赋值。

TReturn Foo<T, TReturn>(T t) 
{
    ...
}

Foo = ...; // ???

显然上述代码是无法通过编译的。综上,给泛型方法的类型参数定义为协变量或者逆变量是没有意义的,因此也没有必要提供这一功能。

3.2.4 泛型类

C#中的泛型类的类型参数同样只允许为不变量,这里以常用的泛型List<>为例,下面的代码是不允许的:

List<Cat> cats = new List<SuperCat>();

哪怕从概念上说一个SuperCat的对象的集合用于需要Cat对象的集合的场景是合法的,但是这一行为确实是不允许的,原因是CLR不支持。此外,C#限制协变量只能为方法的返回类型(后文会解释),所以下面的类定义是不可行的:

class Foo<out T>
{
    public T Get() { }              // 可以,协变量用于返回类型
    public Set(T arg) { }           // 错误,协变量不可用于方法参数
    public T Field;                 // 错误,参数类型T既不是作为方法的返回类型,也不是作为方法的参数
}

既然连字段的类型都不能是协变的泛型类型,那么显然这样的类没有太大的意义。由于以上原因,泛型变体对于定义泛型类的意义不大。

 

4. 变体限制

C#对泛型中允许变体的类型参数有严格的使用限制,主要限制如下:

  1. 协变量只能作为输出参数(方法的返回值,不包out参数)
  2. 逆变量只能作为输入参数(方法的参数,不包括in、out以及ref参数)
  3. 只能是不变量、协变量或者逆变量三者之一

上述限制也说明了为何C#选择用out关键字来修饰协变量,in关键字来修饰逆变量。如果没有以上限制,可能出现一些很奇怪的操作,例如:

(1)假设:协变量可用于输入参数:

delegate void Action<out T>(T arg); // 此处协变量T作为了方法参数

void Call(SuperCat cat)
{

}

Action<Cat> f = GetCat;

上述代码中当委托f被调用时可能会传入一个Cat对象,然而其引用Call方法需要的是一个SuperCat对象,此时Cat类型无法安全转化为SuperCat类型,因此会出现运行时错误。

(2)假设:逆变量可用于方法的输出参数

delegate T Func<in T>(); // 此处类型参数T作为了方法返回类型

Cat GetCat()
{
    ...
}

Func<SuperCat> f = GetCat;

上述代码中当委托f被调用后,应当返回一个SuperCat对象,然而其引用的GetCat方法返回的只是一个Cat对象,同样,会出现运行时错误。

从上述例子中可以看出,对变体的适用范围进行限制显然有助于提高编写更安全的代码。

 

6. 变体杂谈

6.1 历史问题

C#的数组支持协变,也就是说下面的代码是允许的:

Cat[] cats = new SuperCat[10];

咋一看没什么问题,SuperCat的数组当然可以安全转化为Cat数组使用,然而这意味着下述代码也能通过编译:

object[] objs = new Cat[10];
objs[0] = new Dog();

但显然这会在运行时出现错误。数组协变在某些场合下可能有用,但很多时候错误的使用或者误用会导致没必要的运行时错误,因此应当尽可能避免使用这一特性。

6.2 缺点

使用变体要求类型可以在引用类型的层面上进行转换,简单来说就是变体只作用于引用类型之间。因此尽管object是所有类型的基类,但是下述代码依然无法通过编译:

IEnumerable<object> data = new List<int>();

这是由于int为值类型,显然值类型无法在引用类型层面转化为object。

浏览器报Mixed Content错误的解决 - 简书

mikel阅读(1755)

来源: 浏览器报Mixed Content错误的解决 – 简书

控制台报错:

Mixed Content: The page at ‘https://xxx.cn’ was loaded over HTTPS, but requested an insecure script ‘http://res.wx.qq.com/open/js/jweixin-1.6.0.js’. This request has been blocked; the content must be served over HTTPS.

image.png

 

解决方案

第一种
1.首先确定引入的资源可以在http和https下都能访问
比如:http://res.wx.qq.com/open/js/jweixin-1.6.0.js —> https://res.wx.qq.com/open/js/jweixin-1.6.0.js
2.在head里面写类似相对路径的形式

<script src="//res.wx.qq.com/open/js/jweixin-1.6.0.js" type="text/javascript"></script> 

第二种
1.在页面中加入(meta)头中添加upgrade-insecure-requests

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

2.这将会把http请求转化为https请求。这样就不会再出现Mixed Content的错误了。

作者:jia林
链接:https://www.jianshu.com/p/0530240043e8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

错误:Mixed Content: The page at ‘https://XXX’ was loaded over HTTPS, but requested an insecure......._cczzyc1的博客-CSDN博客

mikel阅读(580)

来源: 错误:Mixed Content: The page at ‘https://XXX’ was loaded over HTTPS, but requested an insecure……._cczzyc1的博客-CSDN博客

HTTPS页面里动态的引入HTTP资源,比如引入一个js文件,会被直接block掉的.在HTTPS页面里通过AJAX的方式请求HTTP资源,也会被直接block掉的。

Mixed Content: The page at ‘xxx’ was loaded over HTTPS, but requested an insecure resource ‘xxx’. This request has been blocked; the content must be served over HTTPS.

解决办法:

页面的head中加入:

<meta http-equiv=”Content-Security-Policy” content=”upgrade-insecure-requests”>

意思是自动将http的不安全请求升级为https

注意:在需要的网页上加上面的语句,

其它不需要的网页不需要加,不然后了出错的。

CSP设置upgrade-insecure-requests

好在 W3C 工作组考虑到了我们升级 HTTPS 的艰难,在 2015 年 4 月份就出了一个 Upgrade Insecure Requests 的草案,他的作用就是让浏览器自动升级请求。

在我们服务器的响应头中加入:

header("Content-Security-Policy: upgrade-insecure-requests");
  • 1

 

转载:https://blog.csdn.net/u012259256/article/details/72039950

https://www.cnblogs.com/hustskyking/p/upgrade-insecure-requests.html

https://blog.csdn.net/haibo0668/article/details/82947917

百度地图引用报错A parser-blocking, cross site (i.e. different eTLD+1) script_雨中畅游的博客-CSDN博客

mikel阅读(1237)

来源: 百度地图引用报错A parser-blocking, cross site (i.e. different eTLD+1) script_雨中畅游的博客-CSDN博客

最近公司在搞vue项目,自己独立学习从头开始呀,都是泪。踩了好多坑,正好今天做到一个地方引入百度地图,之前做angular也做过,但是没有遇到这类问题。

百度地图引入首先要申请ak哈。这个网上很多东西。

自己遇到的是一个警告,看到控制台有东西就不爽,强迫症,看图:

 

反正后面一大串,这个在控制台很难看,虽然不影响效果。

解决办法:

直接将我们引入的api地址:

<script src=”http://api.map.baidu.com/api?v=2.0&ak=bUqWGO221Psm3MKPP3a7dKW7SepaA”></script>
后面ak是不正确的,自行注册哈,这里面的东西改一下,也就是api改成getscript就行

<script src=”http://api.map.baidu.com/getscript?v=2.0&ak=bUqWGO221Psm3MKPP3a7dKW7SepaA”></script>
然后刷新,瞬间控制台就干净了,舒服多了…

原因:其实就是说页面渲染使用了document.write(),感兴趣可以查询

参考大神链接:https://blog.csdn.net/b809220024/article/details/72565978
————————————————
版权声明:本文为CSDN博主「雨中畅游」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/viewyu12345/article/details/80705114

php7链接mysql8报错SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)_哦卖糕的博客-CSDN博客

mikel阅读(643)

来源: php7链接mysql8报错SQLSTATE[HY000] [1045] Access denied for user ‘root’@’localhost’ (using password: YES)_哦卖糕的博客-CSDN博客

使用环境:
windows 7 下
wampserver 3.2.0-64bit
mySQL版本8.0.18 端口号:3308
php版本7.3.12
测试的源码:

<?php
$servername = “localhost”;
$username = “root”;
$password = “root”;

try {
$conn = new PDO(“mySQL:host=$servername;”, $username, $password);
echo “连接成功”;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

报错内容:
SQLSTATE[HY000] [1045] Access denied for user ‘root’@‘localhost’ (using password: YES)
网上找到很多关于修改 mi.ini ,httpd.conf ,httpd-vhost.conf,等文件的文章,最终测试的都不行。
以下是举例:
1.确认database.php文件配置正确。
2.检查.env文件

1.APP_ENV=local
2.APP_KEY=
3.APP_Debug=true
4.APP_LOG_LEVEL=Debug
5.APP_URL=http://localhost

6.DB_CONNECTION=mysql
7.DB_HOST=127.0.0.1
8.DB_PORT=3308
9.DB_DATABASE=homestead
10.DB_USERNAME=homestead
11.DB_PASSWORD=secret

12.BROADCAST_DRIVER=log
13.CACHE_DRIVER=file
14.SESSION_DRIVER=file
15.QUEUE_DRIVER=sync

16.REDIS_HOST=127.0.0.1
17.REDIS_PASSWORD=null
18.REDIS_PORT=6379

19.MAIL_DRIVER=smtp
20.MAIL_HOST=mailtrap.io
21.MAIL_PORT=2525
22.MAIL_USERNAME=null
23.MAIL_PASSWORD=null
24.MAIL_ENCRYPTION=null

25.PUSHER_APP_ID=
26.PUSHER_KEY=
27.PUSHER_SECRET=
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
取对应的数据库部分

“`php
1.DB_CONNECTION=mysql
2.DB_HOST=127.0.0.1
3.DB_PORT=3308
4.DB_DATABASE=homestead
5.DB_USERNAME=homestead
6.DB_PASSWORD=secret
1
2
3
4
5
6
7
8
3.修改.env文件。

1.DB_CONNECTION=mysql
2.DB_HOST=[你的数据库地址]
3.DB_PORT=[端口(3308)]
4.DB_DATABASE=[数据库]
5.DB_USERNAME=[用户名]
6.DB_PASSWORD=[密码]
1
2
3
4
5
6
4、检查httpd.conf。
5、httpd-vhost.conf

以上方法都不能解决问题,后来换了一串代码,如下:

<?php
$serve = ‘mysql:host=localhost:3308;dbname=hzpccc;charset=utf8’;
$username = ‘root’;
$password = ‘root’;

try{ // PDO连接数据库若错误则会抛出一个PDOException异常
$PDO = new PDO($serve,$username,$password);
$result = $PDO->query(‘select * from hydt’);
$data = $result->fetchAll(PDO::FETCH_ASSOC); // PDO::FETCH_ASSOC表示将对应结果集中的每一行作为一个由列名索引的数组返回
print_r($data);
} catch (PDOException $error){
echo ‘connect failed:’.$error->getMessage();
}

?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
带上端口号就没问题了,怎么回事?我是新手,感觉端口不对才找到这个代码,大神可以指点下什么原因。
————————————————
版权声明:本文为CSDN博主「哦卖糕」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/omaigao/article/details/104476516

使用宝塔创建PHP网站,出现"no input file specified"怎么办? - 知乎

mikel阅读(509)

来源: 使用宝塔创建PHP网站,出现”no input file specified”怎么办? – 知乎

使用宝塔建站也有很长一段时间了,一般情况都很正常,偶尔也会出现一些莫名其妙的问题,都能快速解决。
但是今天建站的时候却遇到了一个很奇怪的问题:先是出现”no input file specified”错误,后来出现500错误。百度、搜狗搜索之后,发现很多人都遇到过相似的问题,一堆专家在下面回答,却几乎没有可用的解决方案,提出的问题也没有得到最终的解决。
后来经过对各种线索和日志的分析,已经解决了这个问题,这里整理出来。也许这是全网唯一的可用方案,不是说技术独此一家,别人都不会,我还没有这么高傲自大;而是也许有人会,但不愿或懒得做这种编写文档的事情,那么现在遇到这种问题的人,真是只能绕弯想其他办法了。
此文编写+实验环境重建,费时2小时,如果你有缘看到,并且觉得有用,希望花一秒钟时间在下面点个赞再走吧!

一、问题模拟

因为问题已经解决,那么只能重新试图还原问题的现场。于是从头做了一次,把详细的过程写下来,为大家避坑。

  1. 首先登录到宝塔系统中。我的系统是基于Centos7下安装的宝塔,Windows版本可能有稍许不同。如果大家都用过宝塔,这里就不再啰嗦登录过程了,如果有问题可以私信。
  2. 建一个网站,这里用test.xxx.com来表示。这里没有放我自己的域名,就用xxx.com来表示一级域名吧,加了一个二级域名test。在输入域名时,会自动在www/wwwroot/下建立这个网站的根目录(与域名同名) 。下文中如果出现一级域名被打码,都是指xxx.com

1、进入域名管理,把这个test解析到服务器的IP地址。

2、进入服务器管理,把test.xxx.com添加进白名单。

请注意,第3,4两步不在同一界面!

因为每个ISP界面可能不一样,此处就不截图了。如果有问题,同样可以私信询问!

3、此时,刚刚添加的网站已经可以通过二级域名访问了。

4、下面上传网站文件到根目录,删除创建网站时自动生成的index.html和404.html,因为我们有了自己的入口文件index.php。

5、尝试打开网站test.xxx.com,果然再次出现了错误提示

二、查错过程

此时,我要说两个重要的东西:.user.ini 和 .htaccess ,这两个文件也是在创建网站时自动生成的。
.htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置;.user.ini则指定了PHP中的basedir,个人理解就是网站的运行目录。

这两个文件不能删

“No input file specified.”意思即没有指定输入文件。接下来排错。

1、首先想到的是没有运行index.php,于是把文件中内容修改为只显示一行文本,果然刷新后依然没有显示,说明确实没有运行index.php,也就是说可能问题出在Apache、php或网站的配置上。

2、排除了PHP版本的问题

3、打开宝塔中此网站设置,这里主要看了伪静态和配置文件两个板块。创建网站后伪静态里面其实是空白的,没有什么设置;而配置文件中看起来也没有什么 问题。

其实在解决问题的过程中还是乱填了一些东西,因为都是从网上搜索出来的,所以都没有成功,最后还是恢复成了默认的样子。

4、在设置里面没有实质性的突破后,回到服务器上查找问题。/www/下找到wwwlogs文件夹,必定是保存的运行日志,大概可以从这里去找找问题

5、ls下来,可以看到里面是所有已建网站的运行日志和错误日志。

6、找到不能打开的网站的错误日志,more一下。发现里面讲到“is not within the allowed path(s):”这个目录不在允许的路径中。

三、解决方案

日志中提到这个目录不在允许的路径中。其实很说明问题。
为什么不被允许?允许的路径是哪里?是否加入到允许的路径就可以被访问?
带着这个问题进行思考,我们通常会发现,出现问题时走了很多弯路,而在通过对各种线索的分析后,真相会逐渐浮出水面。
既然错误提示我们权限的问题,那么就从权限上去找答案。
宝塔里面所建立的网站,是可以被访问的,这在建站完成时证明是成功的,说明宝塔系统没有问题。
then?
Apache管理WEB服务器的环境,也是没有问题。
PHP56管理PHP环境,这就是经过推理后的症结所在。
这里讲的当然是宝塔里面的PHP56。
我们知道PHP的配置文件是php.ini,可是宝塔里的PHP配置文件在哪里呢?

进入到PHP的配置界面

按ctrl+f,打开窗口搜索界面,搜索什么内容呢?
从前面错误日志的分析来看,新网站的目录没有加进open_basedir,也就是说其他能访问的网站都在这个文件夹里!
所以搜索open_basedir,或者/www/wwwroot/,也就是www的根目录。结果非常理想,很快就得到我想寻找的内容 。

果然在open_basedir下没有新建网站的目录。注意一下“open_basedir=”后面的路径是用 : 来分隔,所以在最后加上”:/www/wwwroot/test.xxx.com“这个路径,重启PHP,再次测试,正常打开网站。

至此,这个问题完美解决。
可能解决的方法非常简单,但在处理的过程中,其实经历了很多曲折。

码字不易,如果你有缘看到,并且觉得有用,希望花一秒钟时间在下面点个赞再

【基础篇】做了这么久,才知道什么是上位机 - 知乎

mikel阅读(574)

来源: 【基础篇】做了这么久,才知道什么是上位机 – 知乎

一、定义

上位机:
上位机指可以直接发送操作指令的计算机或单片机,一般提供用户操作交互界面并向用户展示反馈数据。
典型设备类型:电脑,手机,平板,面板,触摸屏

下位机:
下位机指直接与机器相连接的计算机或单片机,一般用于接收和反馈上位机的指令,并且根据指令控制机器执行动作以及从机器传感器读取数据。
典型设备类型:PLC,STM32,51,FPGA,ARM等各类可编程芯片

上位机软件:
用于完成上位机操作交互的软件被定义为“上位机软件”;

二、上位机与下位机之间的关系

  • 上位机给下位机发送控制命令,下位机收到此命令并执行相应的动作。
  • 上位机给下位机发送状态获取命令,下位机收到此命令后调用传感器测量,然后转化为数字信息反馈给上位机。
  • 下位机主动发送状态信息或报警信息给上位机。

为了实现以上过程,上位机和下位机都需要单独编程,都需要专门的开发人员在各自两个平台编写代码。

上位机与下位机关系示意图:

三、通信协议与通信API

实现上下位机之间的通信需要了解以下2个概念:

  • 通信协议
    上位机和下位机之间的通信协议有很多,只要能完成通信的协议都可以用在上位机与下位机之间。比如:
    通信协议(通信方式):RS232/RS485串行通信、USB、蓝牙、网络UDP/TCP

这个通信协议(通信方式)是实现上位机与下位机之间数据交换的基本通道。

  • 通信API
    在通信协议的基础上,具体发送什么数据即发送什么指令,还需要规定各个功能所对应的指令(上位机发给下位机的指令)。
    每个功能所对应的指令叫做API(Application Programming Interface), 在实际工作中常称这个api为“私有通信协议”。
    举例一条获取温度的api:
    [包头 + 获取温度对应的命令编码(编号)+ 校验位 + 包尾]
    api的命令格式,是自定义的一种固定的数据组合格式。不受任何通信方式和通信平台的限制。

这就意味着,只要通信协议(通信方式)可以建立,上位机软件可以是任意开发语言和任意开发平台,下位机也可以使用任意类型的单片机

四、上位机软件开发的特点

开发上位机软件与其他软件最大的区别就是“上位软件要连接设备并与之通信”。因此引发了上位机软件开发的各项技术:通信方式多样性、私有协议定制、通信框架的产生。

1、应用的场合有:

  • 手机连接智能设备
  • 电脑软件连接工业设备
  • 电脑软件连接医疗仪器
  • 电脑软件连接打印机
  • 其他需要电脑软件控制设备的场合

2、PC软件的没落与前景
随着智能手机和平板电脑的普及,现在大家都很少用电脑了,因为手机给用户提供了很大的使用自由,随时随地使用种类繁多的app。不仅方便高效还非常美观。现在能用手机app代替的软件,都没有人用相应电脑软件了。因此整体上PC软件在持续走衰,买台式机电脑的人也因此大量流失了。
那么PC和PC软件就会没落甚至消失吗?就现在来看是不会(现在2020年)不仅不会还会持续好一段时间。为什么说暂时不会消失呢?是因为还有好些软件手机app和平板电脑无法替代。
app无法代替PC软件的一些原因:

  • 操作:
    PC比手机多了键盘和鼠标,因此操作便捷性吊打手机。
  • 性能
    从配置名字上看,现在手机的cpu和内存都要超过普通电脑配置了,但是其实际性能还和台式机差好远。运行密集计算的绘图和视频相关的软件还是得用PC来完成。
  • 资源
    电脑可以接插大量的外设。比如几十T的硬盘、打印机、投影仪、摄像头等一系列外部资源。可以说吊打手机了。
  • 屏幕尺寸
    电脑都拥有一个大屏幕,操作起来效率非常高,比如excel在大屏幕上编辑起来非常高效。可以开多个软件窗口,随时切换操作。平板电脑的存在和普及就是因为大屏幕。(手机端现在有excel,但是人们还是用PC软件Excel的原因)
  • 电源
    PC一般都接着交流电,虽然不能像手机一样便携,但是拥有了持久稳定的电源。对于一些应用场合还是得使用交流电的方便。使用交流电使得电源功率大也是PC性能高的一个原因。
  • 专用外设
    有些外设必须要用PC电脑操作,比如:网银盾、加密狗、hifi声卡、行业专用采集卡等。
  • 其它
    另一个很奇葩但是有现实存在的原因是,手机相对于PC台式机更容易丢失(这里是指把手机作为专用设备的上位机设备来使用的情况)。

这里列举一些app无法替代电脑软件的例子:

视频剪辑软件、photoshop、CAD、绘制电路板软件、绘制机械三维图软件,彩超等医疗器械对应的上位机软件
行业专用设备配的上位机软件、军工设备配备的上位机软件、程序猿编程软件

除去如上所列的这些特殊软件需要使用PC的优势,剩余的软件均被手机软件所占领。手机软最大胜利就是他的便携性。
当然这也合情合理,出现了新的设备平台瓜分了PC软件的天下。技术总是要前进嘛。未来发展手机也可能会被抛弃。
因此呢,根据现在的情况来看pc软件还有好长一段时间会持续存在。

现在遍地是手机app程序猿,对于pc软件开发者建议是,要么转行要么在特定行业中开发PC软件,这样技术积累才能持久,也能持久发展。
使用PC软件的行业主要有:医疗器械、实验室器械、军工、电力行业。

五、对手机app上位机的一些说明

为了追求便携性方便用户使用,其实很多设备专用软件也尝试开发了app端上位机软件。
但是经实际检验遇到如下方面的问题:

1、操作不方便,对于操作复杂的设备来说,使用app上位机软件时,还要腾出一只手或两只手来操作反而不方便了

2、接外设不方便,遇到最多的是接打印机不方便(虽然能接网络打印机,但实际体验差)

3、通信不方便,虽然可以使用蓝牙或无线网络来控制下位机设备,但是不够稳定,尤其特定行业还使用不了任何无线连接

4、一般工业设备的上位机软件会有多人交替操作,由于手机太便携,因此也同意丢失或被盗。

因此对于特殊行业又想用移动设备的便携性又想获得相对好的操作体验,可以使用运行Android系统的平板。

六、特殊行业的上位设备

肯德基店的点餐大屏幕(Android)、公交车上的广告屏幕(Android)、车站的导航屏幕(PC+win7)、移动手机充电站、自助贩卖机。