DDD领域驱动设计落地实践(十分钟看完,半小时落地) - 只会一点java - 博客园

mikel阅读(835)

来源: DDD领域驱动设计落地实践(十分钟看完,半小时落地) – 只会一点java – 博客园

 

正文

一、引子

不知今年吹了什么风,忽然DDD领域驱动设计进入大家视野。该思想源于2003年 Eric Evans编写的“Domain-Driven Design领域驱动设计”简称DDD,Evans DDD是一套综合软件系统分析和设计的面向对象建模方法。刚好公司领导强力推荐这个,抱着学习的心态,耗时5个月,体验了一把:“DDD从入门到弃坑”。

二、思想

学习网站:https://www.jdon.com/ddd.html

书:领域驱动设计

2.1 服务器后端发展三个阶段

 

服务器后端发展三个阶段:

  1. 面向过程脚本:初始简单,业务复杂后,维护难度指数上升。–>基本不为主流使用
  2. 面向数据库表:初始难度中,业务复杂后,维护难度延迟后再指数上升。—>目前市面上主流
  3. 面向业务模型:DDD+SOA微服务的事件驱动的CQRS读写分离架构:应付复杂业务逻辑,以聚合模型替代数据表模型,以并发的事件驱动替代串联的消息驱动。真正实现以业务实体为核心的灵活拓展。初始难度高,业务复杂后,维护难度线性上升(已很不错)

2.2 DDD最大特点

DDD革命性在于:领域模型准确反映了业务语言,而传统微服务数据对象除了简单setter/getter方法外,没有任何业务方法,即失血模型,那么DDD领域模型就是充血模型(业务方法定义在实体对象中)

 

三、落地

3.1 领域模型设计

以渠道中心(一个微服务)作为例子来做领域模型设计,核心就是设计2个图,一个是战略设计图(宏观)  ,一个是战术设计图(细节)。

1.领域战略设计图

战略设计图是从一个限界上下文的角度出发去分析业务场景。主要是宏观上的核心域、子域、实体关系图。demo如下图:

2.领域战术设计图

战术设计图是从一个限界上下文的角度出发去分析业务场景。细化到核心业务字段、领域实体、值对象、领域服务、领域事件等等。基本上这个图画完,代码已经知道怎么写了。demo如下图:

3.2 技术实现

整体项目框架分层图如下所示:

如上图,4层典型DDD分层结构,

1.展现层:controller层。无业务逻辑

2.应用服务层:此层可以包含查询逻辑,但核心业务逻辑必须下沉到领域层。

3.领域服务层:业务在这里组装。仓储(资源库)接口在此层定义。

4.基础设施层:仓储(资源库)实现层+PO持久化层。

注:

1.简单查询不涉及业务,是可以直接从应用层直接穿透到PO查询,不需要经过domain层。如下图所示,DDD本身是不限制非业务类操作跨层调用的。

 

2.DTO是不能存在于domain层的,DDD设计不认为DTO是业务对象,entity才是。或者传值简单数据类型也是可以的。

3.2.1 服务调用问题

1.域内调用

领域内调用,随便调用,丝般顺滑。至于实现,可以由一个核心域的仓储实现层(第四层)去实现多个Repository接口。(比如这里A是核心域的实体名,B是支撑域、通用域等)

 2.跨域调用

 

 

跨域分为

  • 1.同上下文跨域:ACL层->Adapter适配器层→调用其它域的repository。—>不得已才使用,不推荐使用。
  • 推荐:1.使用领域事件 eventbus来做解耦

               2.考虑是否有可能合并为一个领域.

  • 2.跨上下文(肯定跨域):ACL层->Adapter适配器层->feign调用

3.2.2 包结构

包结构如下:

 展开包结构如下:

展现层:Controller,仅做接口的入口定义和编排转发,不做任何的业务处理;

应用服务层:application,负责接口参数DTO的简单校验,以及DTO和实体值对象的数据转换,对于简单的业务,也可以在应用层加载实体直接执行实体行为方法;

领域层:

  • 模型:根据领域模型分析领域内各实体、聚合、聚合根、值对象等,这些对象在*.domain.model定义,实体内的行为方法只负责维护实体自身的生命周期和状态;
  • 行为:领域内各实体、聚合、聚合根等,会有相应的行为,在*.domain.model包下定义行为方法;
  • 领域服务:领域提供的接口服务,需要定义在*.domain.service包下,业务相关的前置业务判断、多个实体或值对象的行为逻辑处理等,都在领域服务中实现,需要注意的是并不是每个实体都有一个对应的领域服务,但是依赖多个实体的行为方法,最好根据这个业务模块是建立一个领域服务;
  • 仓储:领域服务或上层应用服务需要使用到的基础设施层,包括DB、Feign调用等,定义在*.domain.repository下,在*.infrastructure.repository下实现;

适配层:在acl包下的feign定义依赖外部的接口,并在acl的adapter包编写转换,由仓储层操作实体时调用;

持久层:与常用DAO定义一致,由仓储层操作实体时调用。

 3.2.3 技术架构

目前业内没有标杆,github开源地址:https://github.com/jovezhao/nest  。作者不是本人哈,这个项目可以练手DDD。

 

四、总结

DDD可以尝试,但不建议主流业务硬上。建议浅尝即止。(据我所知,业内连阿里巴巴都没有大面积推广。)

宝塔面板下nginx屏蔽垃圾蜘蛛禁止抓取不影响火车头发布_seoshen的博客-CSDN博客

mikel阅读(779)

来源: 宝塔面板下nginx屏蔽垃圾蜘蛛禁止抓取不影响火车头发布_seoshen的博客-CSDN博客

最近查看服务器日志,发现一些垃圾蜘蛛,一直爬行很多,比如以下这些垃圾,太烦人了。

Mozilla/5.0 (compatible; SemrushBot/6~bl; +http://www.semrush.com/bot.html)”
Mozilla/5.0 (compatible; AhrefsBot/6.1; +http://ahrefs.com/robot/)”
1
2
本人一直使用的linux系统服务器宝塔面板管理,个人感觉比较方便。网上查找方法,屏蔽这些垃圾。

网上找到的代码如下:目前为止比较好用。

#禁止Scrapy等工具的抓取

if ($http_user_agent ~* (Scrapy|Curl|HttpClient)) {

return 403;

}

#禁止指定UA及UA为空的访问

if ($http_user_agent ~* “FeedDemon|Indy Library|Alexa Toolbar|AskTbFXTV|AhrefsBot|CrawlDaddy|CoolpadWebkit|Java|Feedly|UniversalFeedParser|ApacheBench|Microsoft URL Control|Swiftbot|ZmEu|oBot|jaunty|Python-urllib|lightDeckReports Bot|YYSpider|DigExt|HttpClient|MJ12bot|heritrix|Bytespider|Ezooms|Googlebot|JikeSpider|SemrushBot|^$” ) {

return 403;

}

#禁止非GET|HEAD|POST方式的抓取

if ($request_method !~ ^(GET|HEAD|POST)$) {

return 403;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
宝塔面板下使用方法如下:
1、找到文件目录/www/server/nginx/conf文件夹下面,新建一个文件

agent_deny.conf

内容就是以上代码。

2、找到网站设置里面的第7行左右 写入代码: include agent_deny.conf;

root /www/wwwroot/www.SEOshen.com;
include agent_deny.conf; (就是添加到这一行的位置。)
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
1
2
3
如果你网站使用火车头采集发布,使用以上代码会返回403错误,发布不了的。如果想使用火车头采集发布,请使用下面的代码

#禁止Scrapy等工具的抓取

if ($http_user_agent ~* (Scrapy|Curl|HttpClient)) {

return 403;

}

#禁止指定UA访问。UA为空的可以访问,比如火车头可以正常发布。

if ($http_user_agent ~ “FeedDemon|Indy Library|Alexa Toolbar|AskTbFXTV|AhrefsBot|CrawlDaddy|CoolpadWebkit|Java|Feedly|UniversalFeedParser|ApacheBench|Microsoft URL Control|Swiftbot|ZmEu|YandexBot|jaunty|Python-urllib|lightDeckReports Bot|YYSpider|DigExt|HttpClient|MJ12bot|heritrix|Bytespider|Ezooms|Googlebot|JikeSpider|SemrushBot” ) {

return 403;

}

#禁止非GET|HEAD|POST方式的抓取

if ($request_method !~ ^(GET|HEAD|POST)$) {

return 403;

}
————————————————
版权声明:本文为CSDN博主「SEOshen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/SEOshen/article/details/105055430

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

mikel阅读(912)

来源: 三步实现低代码框架 – 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阅读(1455)

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

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阅读(2067)

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

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

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

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

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

mikel阅读(849)

来源: .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阅读(1952)

来源: 浏览器报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阅读(665)

来源: 错误: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阅读(1463)

来源: 百度地图引用报错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阅读(743)

来源: 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