干货来袭-整套完整安全的API接口解决方案 - hubro - 博客园

mikel阅读(695)

来源: 干货来袭-整套完整安全的API接口解决方案 – hubro – 博客园

在各种手机APP泛滥的现在,背后都有同样泛滥的API接口在支撑,其中鱼龙混杂,直接裸奔的WEB API大量存在,安全性令人堪优

在以前WEB API概念没有很普及的时候,都采用自已定义的接口和结构,对于公开访问的接口,专业点的都会做下安全验证,数据签名之类

反而现在,谁都可以用WEB API估接口,安全性早忘一边了,特别是外包小公司的APP项目,80%都有安全漏洞(面试了大半年APP开发得出的结论)

特在过年之前,整理了下在用的解决方案,本方案解决了

  • 数据安全问题
  • 标准消息结构
  • 接口测试程序
  • 接口文档体现

正文

数据结构

对于一个接口,返回的内容除了要返回业务数据外,还得返回处理状态,并且这个状态是在每个接口都得有

所以数据格式都会定义为:

数据头(描述数据信息)

———————————–

数据体(具体数据)

本文定义结构为

复制代码
/// <summary>
    /// 处理结果
    /// </summary>
    public class DealResult
    {
        /// <summary>
        /// 处理结果
        /// </summary>
        public bool Result
        {
            get;
            set;
        }
        /// <summary>
        /// 消息
        /// </summary>
        public string Message
        {
            get;
            set;
        }
        /// <summary>
        /// 关联数据
        /// </summary>
        public object Data
        {
            get;
            set;
        }
    }
复制代码

所有接口都返回此对象,会描述本次请求的状态,和对应的数据,服务端则根据实际情况,返回处理结果和对应的数据

 

数据安全

开方式接口安全性就不用多说了,解决方法为加密,或数据签名验证,本文方案为进行数据签名

同返回的数据一样,提交到服务器的数据格式也统一约定,定义一个数据头基类

复制代码
    /// <summary>
    /// 参数基类
    /// </summary>
    [Serializable]
    public class ParameBase
    {
        string time = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");
        /// <summary>
        /// 时间 格式 yyyy-MM-dd hh:mm:ss
        /// </summary>
        public string Time
        {
            get
            {
                return time;
            }
            set
            {
                time = value;
            }
        }
        /// <summary>
        /// 来源网站 = 1, IOS = 2,Android = 3, 微信 = 4
        /// </summary>
        public int SourceFrom
        {
            get;
            set;
        }
        /// <summary>
        /// 签名
        /// </summary>
        public string Token
        {
            get;
            set;
        }
       
    }
复制代码

 

一个登录对象表示为

复制代码
    /// <summary>
    /// 登录
    /// </summary>
    public class Login : ParameBase
    {
        /// <summary>
        /// 用户名
        /// </summary>
        public string Name
        {
            get;
            set;
        }
        /// <summary>
        /// 密码
        /// </summary>
        public string Password
        {
            get;
            set;
        }
    }
复制代码

数据签名表示为(KEY稍后讲到)

Token=MD5(属性值1+值2….+KEY)

按此对象表示为 MD5(Name+PassWord+Source+Time+KEY)

如果是GET参数怎么办,一样,按参数名计算,同时传递的参数要附带上Source,Time,Token

 

密钥机制

有的喜欢把密钥放在客户端,或固定密钥,显然都有安全问题,解决方法是动态获取

这就意味着在设计接口时,有一个接口是首先要调用的,让服务器返回密钥,于是就有了登录的概念

过程表示为

登录>返回用户信息和密钥=>存储用户信息和密钥=>使用密钥调用其它接口

这样只有登录者和服务器才知道自已的密钥了

综上所述,数据结构表示为

客户端提交结构为 ParameBase(附带签名信息)

服务端返回结构为 DealResult

 

登录机制

同网页请求一样,怎么知道多次调用是同一个人呢,这里采用了COOKIE的形式,登录后服务端返回一个COOKIE,客户端再请求时带上这个COOKIE

服务端需要存储这个COOKIE标识,所有的验证处理都会基于此标识来判断用户

 

有了上面基础,进入项目阶段

WEB API项目

其实用什么项目类型都行,只是WEB API方便了对象结构序列化和传参

默认WEB API路由RESUFUL形式,没有控制器方法,只能按METHOD来定义,很不方便,改成控制器的形式,这样就能用方法名来访问了

更改路由配置为

1
2
3
4
5
config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",//加上路由ACTION参数
                defaults: new { id = RouteParameter.Optional }
            );

在此文,数据分为请求和返回,以登录返回用户信息为例,登录为请求,用户信息为返回,示例对象结构为

用户对象

复制代码
/// <summary>
    /// 登录返回用户
    /// </summary>
    public class User
    {
        /// <summary>
        /// 用户编号
        /// </summary>
        public int Id
        {
            get;
            set;
        }
        /// <summary>
        /// 名称
        /// </summary>
        public string Name
        {
            get;
            set;
        }
        /// <summary>
        /// 本次登录的KEY
        /// </summary>
        public string Key
        {
            get;
            set;
        }
        /// <summary>
        /// 本资登录的凭证
        /// </summary>
        public string Voucher
        {
            get;
            set;
        }

       
    }
复制代码

 

请求方式

这里只采用了GET,POST两种方式,根据实际情况定义,控制器方法一定需要都标明,不然会出现路由BUG

定义登录方法

复制代码
/// <summary>
        /// 登录
        /// </summary>
        /// <param name="parame"></param>
        /// <returns>User</returns>
        [HttpPost]
        [AnonymousSign]
        public DealResult Login([FromBody] Login parame)
        {
            if (parame.Password != "123")
            {
                return DealResult(false, "密码不正确");
            }
            string key2 = System.Guid.NewGuid().ToString();
            string voucher = System.Guid.NewGuid().ToString();
            var user = new User() { Name = parame.Name, Id = 1, Key = key2, Voucher = voucher };
            var timeDiff = (DateTime.Now - Convert.ToDateTime(parame.Time)).TotalSeconds;//保存客户端和服务端时间差
            LoginStatusContext.SetLoginStatus(voucher, user.Id, key2, timeDiff);
            CoreHelper.CookieHelper.AddCookies("user", voucher);//存入COOKIE
            return DealResult(true, "", user);
        }
复制代码

这里可以看到,创建了两个GUID,一个为用户凭证,一个为用户密钥,放入用户信息返回,同时调用LoginStatusContext.SetLoginStatus保存登录信息

同时使用了AnonymousSign标注,此方法使用默认签名Setting.DefaultKey

定义获取用信息方法

复制代码
        /// <summary>
        /// 基本信息
        /// </summary>
        /// <param name="name">参数name</param>
        /// <returns>User</returns>
        [HttpGet]
        public DealResult GetBasicInfo(string name)
        {
            var user = new User() { Name = name, Id = CurrentUserId };
            return DealResult(true, string.Empty, user);
        }
复制代码

 

示例控制器完整定义

复制代码
 /// <summary>
    /// 帐号操作
    /// </summary>
    [SignCheckAttribute]
    public class AccountController : BaseController
    {
        /// <summary>
        /// 登录
        /// </summary>
        /// <param name="parame"></param>
        /// <returns>User</returns>
        [HttpPost]
        [AnonymousSign]
        public DealResult Login([FromBody] Login parame)
        {
            if (parame.Password != "123")
            {
                return DealResult(false, "密码不正确");
            }
            string key2 = System.Guid.NewGuid().ToString();
            string voucher = System.Guid.NewGuid().ToString();
            var user = new User() { Name = parame.Name, Id = 1, Key = key2, Voucher = voucher };
            var timeDiff = (DateTime.Now - Convert.ToDateTime(parame.Time)).TotalSeconds;//保存客户端和服务端时间差
            LoginStatusContext.SetLoginStatus(voucher, user.Id, key2, timeDiff);
            CoreHelper.CookieHelper.AddCookies("user", voucher);//存入COOKIE
            return DealResult(true, "", user);
        }


        /// <summary>
        /// 基本信息
        /// </summary>
        /// <param name="name">参数name</param>
        /// <returns>User</returns>
        [HttpGet]
        public DealResult GetBasicInfo(string name)
        {
            var user = new User() { Name = name, Id = CurrentUserId };
            return DealResult(true, string.Empty, user);
        }

        /// <summary>
        /// 测试异常
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public DealResult TestException()
        {
            int a = 0;
            var b = 10 / a;
            return DealResult(true);
        }

    }
复制代码

 

此控制器标注了SignCheckAttribute用以进行签名判断

具体实现可看SignCheckAttribute代码

SignCheckAttribute里实现了有

  • 数据签名判断
  • 签名超时判断
  • 用户登录限制
  • 签名重复使用处理(一个签名只能使用一次)
  • 过期登录用户处理(没有主动退出用户清理)

为了统一处理异常,配置了异常处理

1
GlobalConfiguration.Configuration.Filters.Add(new ExceptionAttribute());

对接口进行测试

大杀器来了,配合此方案放出了对应的测试工具,虽然WEB API有个扩展,但没法对此方案测试

使用此工具能方便按方案要求调用接口,为了方便参数拼接,POST和GET都采用URL参数的形式输入

测试登录/api/account/login

测试获取信息/api/account/GetBasicInfo

测试异常处理/api/account/TestException

在未登录情况下调用获取信息

接口文档

接口结构文档一直是很让人头疼的事,手写更改了又得维护,版本不一样还麻烦,自动生成最好了,同样WEB API 带扩展没法表示此结构详细

大杀器2号来了,按代码注释动态生成接口文档,文档格式与控制器保持一致

Home控制器代码实现

复制代码
    public ActionResult Index(SummaryAnalysis.ExportType exportType = SummaryAnalysis.ExportType.NONE)
        {
            if (exportType != SummaryAnalysis.ExportType.NONE)
            {
                var str = SummaryAnalysis.Load(exportType);
                return File(str, "application/octet-stream", "Model_" + exportType + ".zip");
            }
            else
            {
                if (string.IsNullOrEmpty(outPut))
                {
                    outPut = SummaryAnalysis.Load(exportType);
                }
                ViewBag.OutPut = outPut;
                return View();
            }
        }
    }
复制代码

在见过的开发文档,我觉得这是最好的展现形式了,还有锚点,快速定位到对象结构,并且与源代码保持一致

附WEB API 自带文档生成区别

附上项目源码

http://pan.baidu.com/s/1c2rDacK

项目结构:

———-WPF测试程序

———-接口示例

虽然跟CRL快速开发框架无关,但还是加上CRL的名,好文要顶!

打造独特的ORM开发框架 - hubro - 博客园

mikel阅读(947)

来源: 打造独特的ORM开发框架 – hubro – 博客园

ORM一直是长久不衰的话题,各种重复造轮子的过程一直在进行,轮子都一样是圆的,你的又有什么特点呢?

CRL这个轮子造了好多年,功能也越来越标准完备,在开发过程中,解决了很多问题,先上一张脑图描述CRL的功能

开发框架的意义在于

  • 开发更标准,更统一,不会因为不同人写的代码不一样
  • 开发效率更高,无需重新造轮子,重复无用的代码,同时简化开发流程
  • 运行效率得到控制,程序稳定性得到提高

围绕这几点,抛开常规的增删改查,我们来讲些与众不同的

1.与众不同之一,动态数据源,天生适合分库分表

可动态配置的功能总比静态的灵活,扩展性强

目前看到的框架多数访问对象实例化都类似于

var context = new MsSqlContext(ConnectionString);

在对象初始时,就绑定上了数据库连接串, 这样写没什么问题,但是不好扩展
如:需要动态切换库,表,根据租户信息访问不同的数据库,或不同类型的数据库,或是读写分离,这时,急需处理的技术问题就来了,分库分表的解决方案,读写分离的方案
在数据连接绑定的情况下,这种问题很不好解决
又或者传入多个连接串,在调用时,手动选择调用的库或表,对于这种方式,只能说耦合太严重,得关心配置,又得关心调用,在CRL之前的版本里,有这样实现过,弃用了

然而根据IOC的理念,这种问题也不是不好解决,让数据访问对象抽象化实现就能办到了
数据查询方法不再直接调用数据访问对象,而是调用抽象工厂方法,由抽象工厂方法来实例化访问对象,过程表示为

数据查询方法(组件内) => 抽象工厂(组件内) => 抽象实现(组件外)

基于这样的理念,CRL在设计之初,就使用了的这样的方式,以代码为例

!数据访问实现

以下实现了分库分表和mongoDB切换

以下在程序启动时初始

复制代码
var builder = new CRL.SettingConfigBuilder();
            builder.UseMongoDB();//引用CRL.Mongo 使用MongoDB
                                 //注册自定义定位,按MemberSharding传入数据定义数据源位置
                                 //注册一
            builder.RegisterLocation<Code.Sharding.MemberSharding>((t, a) =>
            {
                var tableName = t.TableName;
                if (a.Name == "hubro")//当名称为hubro,则定位到库testdb2 表MemberSharding1
                {
                    tableName = "MemberSharding1";
                    return new CRL.Sharding.Location("testdb2", tableName);
                }
                //返回定位库和表名
                return new CRL.Sharding.Location("testdb", tableName);
            });
            //注册二
            builder.RegisterDBAccessBuild(dbLocation =>
            {
                if (dbLocation.ManageName == "mongo")
                {
                    var conn = CRL.Core.CustomSetting.GetConfigKey("mongodb");
                    return new CRL.DBAccessBuild(DBType.MongoDB, conn);
                }
                return null;
            });
            //注册三
            builder.RegisterDBAccessBuild(dbLocation =>
            {
                //自定义定位,由注册一传入
                if (dbLocation.ShardingLocation != null)
                {
                    return new CRL.DBAccessBuild(DBType.MSSQL, "Data Source=.;Initial Catalog=" + dbLocation.ShardingLocation.DataBaseName + ";User ID=sa;Password=123");
                }
                return new CRL.DBAccessBuild(DBType.MSSQL, "server=.;database=testDb; uid=sa;pwd=123;");
            });
复制代码

 

!数据访问类,类似于仓储的形式,根据实际业务实现
定位使用示例

复制代码
public class MemberManage : CRL.Sharding.BaseProvider<MemberSharding>
{
}
var instance=new MemberManage();
instance.Add(new MemberSharding(){Name="hubro"});
复制代码

根据定位规则 运行到注册一,此数据将会插入到 库testdb2 表MemberSharding1

常规切换示例

1
2
3
4
5
6
public class MongoDBTestManage : CRL.BaseProvider<MongoDBModel2>
{
    public override string ManageName => "mongo";
}
var instance=new MongoDBTestManage();
instance.Add(new MongoDBModel2(){name="hubro"});

根据数据访问规则,运行到注册二,此数据将会插入mongodb

可以看到,在上面代码中,没有看到任何数据连接串的传入,数据的访问都由初始时动态分配,对于方法调用是不透明的,调用者不用关心数据源的问题

2.与众不同之二,表结构自动维护

在新技术的支持下,程序和数据库的绑定关系越来越模糊,现在可能是用的SQLServer,回头可能改成MySQL了,或者改成mongoDB
依赖数据库开发变成越来越不可取,效率也很低
再后来出现了DBFirst方式,虽解决了部份问题,但也很麻烦,如:

建立数据库模型=>导入数据库=>T4模版生成代码(修修补补)

而使用CRL后,过程一步到位,在别人还在用PM设计表结构索引时,你已经设计好了业务结构,效率杠杠的

编写实体类,实现对象访问=>调试运行,自动创建表结构(关键字,长度,索引)

同时,CRL还提供了手动维护方法,使能够按实体结构重建/检查数据表
也提供了对象结构文档导出,不用提心文档的问题
详细介绍看这里
https://www.cnblogs.com/hubro/p/6038118.html

3.与众不同之三,动态缓存

使用缓存可以大大提高程序的运行效率,使用REDIS或MONGODB之类的又需要额外维护
对于单应用程序,程序集内缓存非常有用
CRL内置了缓存实现和维护
只需按方法调用就行了,缓存创建维护全自动
如:
从数据库查

var item = instance.QueryItem(b => b.Id==1)

从缓存查

var item = instance.QueryItemFromCache(b=>b.Id==1);

也支持按查询自定义缓存

复制代码
var query = Code.ProductDataManage.Instance.GetLambdaQuery();
//缓存会按条件不同缓存不同的数据,条件不固定时,慎用
query.Where(b => b.Id < 700);
int exp = 10;//过期分钟
query.Expire(exp);
var list = query.ToList();
复制代码

 

基于这样的形式,可以将所有查询都走缓存,再也不用担心数据库查询效率了,简值中小项目开发利器
详细介绍看这里
https://www.cnblogs.com/hubro/p/6038540.html

4.与众不同之四,应对复杂查询

因为没有查询分支的概念,处理复杂的查询,一票ORM估计得退场了,虽然合理的结构设计会减少查询复杂度,但谁能保证呢
CRL查询分支过程如下

主查询 => CreateQuery子查询 => 返回匿名对象筛选LambdaQueryResultSelect => 主查询嵌套子查询 => 返回结果

理论上只要符合调用逻辑,可以无限嵌套
示例:

复制代码
var q1 = Code.OrderManage.Instance.GetLambdaQuery();//主查询
            var q2 = q1.CreateQuery<Code.ProductData>();//创建一个子查询
            q2.Where(b => b.Id > 0);
            var view = q2.CreateQuery<Code.Member>().GroupBy(b => b.Name).Where(b => b.Id > 0).Select(b => new { b.Name, aa = b.Id.COUNT() });//GROUP查询
            var view2 = q2.Join(view, (a, b) => a.CategoryName == b.Name).Select((a, b) => new { ss1 = a.UserId, ss2 = b.aa });//关联GROUP
            q1.Join(view2, (a, b) => a.Id == b.ss1).Select((a, b) => new { a.Id, b.ss1 });//再关联
            var result = view2.ToList();
            var sql = q1.ToString();
复制代码

生成SQL打印如下

复制代码
SELECT t1.[Id] AS Id,
t2.[ss1] AS ss1
FROM [OrderProduct] t1 with(nolock)
INNER JOIN
(SELECT t2.[UserId] AS ss1,
t3.[aa] AS ss2
FROM [ProductData] t2 with(nolock)
INNER JOIN
(SELECT t3.[Name] AS Name,
COUNT(t3.Id) AS aa
FROM [Member] t3 with(nolock)
WHERE (t3.[Id]>@par1)
GROUP BY t3.[Name]) t3 ON (t2.[CategoryName]=t3.[Name])
WHERE (t2.[Id]>@par0) ) t2 ON (t1.[Id]=t2.[ss1])
复制代码

 

不管是JOIN后再GROUP,还是GROUP后再GROUP,还是GROUP后再JOIN,通通不是问题
详细介绍看这里
https://www.cnblogs.com/hubro/p/6096544.html

5.与众不同之五,查询抽象,非关系型数据库支持

通过对Lambda表达式的解析,可以实现不同的查询转换,如MongoDB,或ElasticSearch(目前只实现了MongoDB)
有人问,这样有什么用呢?
好处就是,在CRL框架下,一套LambdaQuery走天下,不用写各种差异很大的查询方法了,在动态数据源的支持下,数据拆分游刃有余
如:
之前有个报表存在MSSQL里,发现数据量太大了,查询慢,改由MongoDB,程序不用怎么调整,直接在配置里改为MongoDB即可

以MongoDB为例

CRLLambdaQuery=>CRLExpression=>BsonDocument=>MongoDB

在[数据访问实现]示例中,演示了如何切换到MongoDB
代码实现见项目:CRL.Mongo

6.题外之六,请使用仓储模式

在上文提到,好多框架会直接返回一个数据访问对象,如

var obj1context.Query<TestEntity>(b=>b.Id==1).ToSingle();

然而这样会导致滥用,直接在WEB层用,在Service层随意用,如

var obj2=context.Query<TestEntity2>(b=>b.Id==1).ToSingle();
var obj3=context.Query<TestEntity3>(b=>b.Id==1).ToSingle();

某一天,TestEntity3要换库了,查找一下引用,傻眼了,上百个引用(接手别人的项目,亲身体验过这种痛苦,一个个改)
好在CRL开始就杜绝了这种情况发生,对据的访问必须通过BaseProvider实现,而BaseProvider就是一个仓储的形式

7.题外之七,查询效率

ORM效率无非分两点,实体映射效率和语法解析效率,

对于映射反映在,一次返回多行数据,转换为实体集合

对于语法解析效率,按参数调用多次,返回一行数据,转换为实体

测式程序和SQL为本机,CPU空闲正常,2核6G服务器

一张图表明一切(不同机器实际情况可能有差异)

CRL效率虽不是最高的,但也不是最差的,测试项目见:

https://github.com/hubro-xx/CRL5/tree/master/Test/TestConsole

 

大概列举了以上几项,还有好多特有的东西,轮子好不好,东西南北滚滚试试

CRL开发框架虽然写好长时间,但一直在Debug状态中, 最近又升级了,分离了数据访问层,不同数据库引用不同的数据访问层,数据访问层实现也很简单,只需要写两个文件,如MySql,实现MySqlHelper和MySQLDBAdapter
见:https://github.com/hubro-xx/CRL5/tree/master/CRL.Providers/CRL.MySql
同时,版本也升级到5.1,项目结构发生了改变

源码地址:https://github.com/hubro-xx/CRL5

CRL目前.NET版本为.net 4.5, 有时间了再整理整理netstandard版本

除了ORM,CRL还带 动态API,RPC,WebSocket,api客户端代理实现
https://www.cnblogs.com/hubro/p/11652687.html
微服务注册,发现,调用集成参见:
https://github.com/hubro-xx/CRL5/blob/master/Consul/ConsulTest/Program.cs

 

VS2017 签名时出错: 未能对 bin\Debug\app.publish\*.exe 签名。SignTool Error: No certificates were found_cxu123321的博客-CSDN博客

mikel阅读(922)

来源: (1条消息) VS2017 签名时出错: 未能对 bin\Debug\app.publish\*.exe 签名。SignTool Error: No certificates were found_cxu123321的博客-CSDN博客

在使用Visual Studio 2017时遇到项目生成失败的问题,出现错误:签名时出错: 未能对 bin\Debug\app.publish[项目名称].exe 签名。SignTool Error: No certificates were found that met all the given criteria.

 

SignTool Error: No certificates were found that met all the given criteria.

目录

一、简单粗暴的解决办法

这里先说下最简单的方法,只要取消掉项目的ClickOnce清单签名即可,此方法不可滥用,有一定的局限性和要注意的地方,后面会说明原因。具体操作方法如下:

1、 在VS右侧的解决方案资源管理器里找到生成失败的项目。

2、 右键打开项目的属性。

打开项目属性

3、 在属性设置界面中找到签名选项卡。

4、 取消“为ClickOnce清单签名”勾选。

取消签名多选框

完成操作后重新生成

5、 关闭属性设置界面后然后重新生成下项目就可以了。

这个方法虽然简单,但是要根据自己的情况来,不能瞎操作。之所以这样说是因为SignTool Error的问题和签名证书有关,如果项目是属于公司的,或者说这个项目的部署有用到对应的证书,这种情况只能想办法重新安装证书,当然具体怎么操作还是得跟着自己的情况来,最好咨询下项目负责人。

但如果项目本身就没有要用到签名证书的业务,那情况就和我类似,糊里糊涂对项目(Windows应用程序的项目)进了发布操作,随后就莫名其妙的遇到无法生成老报错的情况。我回想了下自己当时操作,大概就是在发布向导界面瞎按了一通完成了发布,随后不小心把对应的证书文件删除了或者做了什么不可描述的操作,结果就出现项目生成时因为签名失败而出错。

PS:其实错误信息也提示很清楚了:”No certificates were found…”,大意就是:签名错误的原因是没有找到符合给定规范的证书,所以要么证书丢了要么证书有问题咯。我的项目是一个控制台应用程序(只是用来测试几段代码的),VS2017用的是社区版本的。

二、总结与归纳

首先要明白VS2017中Windows应用程序的发布/部署默认使用的是ClickOnce技术进行部署,按照官方文档.aspx)的说明:

若要使用 ClickOnce 部署发布应用程序,必须用“公钥/私钥对”为应用程序的部署清单和应用程序清单签名。

所以问题关键点就是围绕着项目部署所使用的签名证书来的,如果仔细留意会发现项目第一次发布后,会自动生成一个.pfx文件(证书文件)。解决方法要么取消相关签名操作,要么修复有问题的证书(可以检查下证书是不是被删除了或者过期之类的)。

几个额外备注:

  1. Windows窗体或控制台应用程序都是属于Windows应用程序。
  2. 关于ClickOnce部署技术,本文没有详细说明,建议另外查阅资料。

版权声明:本文由十有三创作,采用知识共享许可协议:署名-相同方式共享 4.0 国际(CC BY-SA 4.0)。欢迎转载本文,转载请务必署名-保留作者名称及出处:https://shiyousan.com/post/636422963761134191

FreeSql (一)入门 - FreeSql - 博客园

mikel阅读(2155)

来源: FreeSql (一)入门 – FreeSql – 博客园

FreeSQL是功能强大的 .NET ORM,支持 .NetFramework 4.0+、.NetCore 2.1+、Xamarin 等支持 NetStandard 所有运行平台。

支持 MySQL/SQLServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/神通/人大金仓/翰高/MsAccess 数据库。

QQ群:4336577(已满)、8578575(在线)、52508226(在线)

模型

FreeSql 使用模型执行数据访问,模型由实体类表示数据库表或视图,用于查询和保存数据。

可从现有数据库生成实体模型,FreeSql 提供 IDbFirst 接口实现生成实体模型

或者手动创建模型,基于模型创建或修改数据库,提供 ICodeFirst 同步结构的 API(甚至可以做到开发阶段自动同步)。

using FreeSql.DataAnnotations;
using System;

public class Blog
{
    [Column(IsIdentity = true, IsPrimary = true)]
    public int BlogId { get; set; }
    public string Url { get; set; }
    public int Rating { get; set; }
}

声明

dotnet add packages FreeSql.Provider.Sqlite

static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.Sqlite, "Data Source=db1.db")
    .UseAutoSyncStructure(true) //自动同步实体结构到数据库
    .Build(); //请务必定义成 Singleton 单例模式

注意: IFreeSql 在项目中应以单例声明,而不是在每次使用的时候创建。

迁移

程序运行中FreeSql会检查AutoSyncStructure参数,以此条件判断是否对比实体与数据库结构之间的变化,达到自动迁移的目的。

查询

var blogs = fsql.Select<Blog>()
    .Where(b => b.Rating > 3)
    .OrderBy(b => b.Url)
    .Skip(100)
    .Limit(10) //第100行-110行的记录
    .ToList();

插入

var blog = new Blog { Url = "http://sample.com" };
blog.BlogId = (int)fsql.Insert<Blog>()
    .AppendData(blog)
    .ExecuteIdentity();

更新

fsql.Update<Blog>()
    .Set(b => b.Url, "http://sample2222.com")
    .Where(b => b.Url == "http://sample.com")
    .ExecuteAffrows();

删除

fsql.Delete<Blog>()
    .Where(b => b.Url == "http://sample.com")
    .ExecuteAffrows();

FreeSqlBuilder

方法 返回值 说明
UseConnectionString this 设置连接串
UseSlave this 设置从数据库,支持多个
UseConnectionFactory this 设置自定义数据库连接对象(放弃内置对象连接池技术)
UseAutoSyncStructure this 【开发环境必备】自动同步实体结构到数据库,程序运行中检查实体创建或修改表结构
UseNoneCommandParameter this 不使用命令参数化执行,针对 Insert/Update,也可临时使用 IInsert/IUpdate.NoneParameter()
UseGenerateCommandParameterWithLambda this 生成命令参数化执行,针对 lambda 表达式解析
UseLazyLoading this 开启延时加载功能
UseMonitorCommand this 监视全局 SQL 执行前后
UseNameConvert this 自动转换实体、属性名称 Entity Property -> Db Filed
UseExitAutoDisposePool this 监听 AppDomain.CurrentDomain.ProcessExit/Console.CancelKeyPress 事件自动释放连接池 (默认true)
Build<T> IFreeSql<T> 创建一个 IFreeSql 对象,注意:单例设计,不要重复创建

ConnectionStrings

DataType.MySql

Data Source=127.0.0.1;Port=3306;User ID=root;Password=root; Initial Catalog=cccddd;Charset=utf8; SslMode=none;Min pool size=1

DataType.PostgreSQL

Host=192.168.164.10;Port=5432;Username=postgres;Password=123456; Database=tedb;Pooling=true;Minimum Pool Size=1

DataType.SQLServer

Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=1

DataType.Oracle

user id=user1;password=123456; data source=//127.0.0.1:1521/XE;Pooling=true;Min Pool Size=1

DataType.Sqlite

Data Source=|DataDirectory|\document.db; Attachs=xxxtb.db; Pooling=true;Min Pool Size=1

DataType.Firebird

database=localhost:D:\fbdata\EXAMPLES.fdb;user=sysdba;password=123456

DataType.MsAccess

Provider=Microsoft.Jet.OleDb.4.0;Data Source=d:/accdb/2003.mdb

DataType.Dameng(达梦)

server=127.0.0.1;port=5236;user id=2user;password=123456789;database=2user;poolsize=5

DataType.ShenTong(神通)

HOST=192.168.164.10;PORT=2003;DATABASE=OSRDB;USERNAME=SYSDBA;PASSWORD=szoscar55;MAXPOOLSIZE=2

DataType.KingbaseES(人大金仓)

Server=127.0.0.1;Port=54321;UID=USER2;PWD=123456789;database=TEST;MAXPOOLSIZE=2

DataType.OdbcMySql

Driver={MySQL ODBC 8.0 Unicode Driver}; Server=127.0.0.1;Persist Security Info=False; Trusted_Connection=Yes;UID=root;PWD=root; DATABASE=cccddd_odbc;Charset=utf8; SslMode=none;Min Pool Size=1

DataType.OdbcSQLServer

Driver={SQL Server};Server=.;Persist Security Info=False; Trusted_Connection=Yes;Integrated Security=True; DATABASE=freesqlTest_odbc; Pooling=true;Min Pool Size=1

DataType.OdbcOracle

Driver={Oracle in XE};Server=//127.0.0.1:1521/XE; Persist Security Info=False; Trusted_Connection=Yes;UID=odbc1;PWD=123456; Min Pool Size=1

DataType.OdbcPostgreSQL

Driver={PostgreSQL Unicode(x64)};Server=192.168.164.10; Port=5432;UID=postgres;PWD=123456; Database=tedb_odbc;Pooling=true;Min Pool Size=1

DataType.OdbcDameng (达梦)

Driver={DM8 ODBC DRIVER};Server=127.0.0.1:5236; Persist Security Info=False; Trusted_Connection=Yes; UID=USER1;PWD=123456789

DataType.OdbcKingbaseES (人大金仓)

Driver={KingbaseES 8.2 ODBC Driver ANSI};Server=127.0.0.1;Port=54321;UID=USER2;PWD=123456789;database=TEST

DataType.Odbc

Driver={SQL Server};Server=.;Persist Security Info=False; Trusted_Connection=Yes;Integrated Security=True; DATABASE=freesqlTest_odbc; Pooling=true;Min pool size=1

Packages

Package Name Version 说明
FreeSql.Repository NETStandard2.0、net45、net40 通用仓储 + UnitOfWork 实现
FreeSql.DbContext NETStandard2.0、net45、net40 EFCore 的使用风格实现
FreeSql.Provider.MySql NETStandard2.0、net45、net40 基于 MySql.Data(Oracle官方)
FreeSql.Provider.MySqlConnector NETStandard2.0、net45 基于 MySqlConnector
FreeSql.Provider.PostgreSQL NETStandard2.0、net45 基于 PostgreSQL 9.5+
FreeSql.Provider.SqlServer NETStandard2.0、net45、net40 基于 SqlServer 2005+
FreeSql.Provider.SqlServerForSystem NETStandard2.0、net45、net40 基于 System.Data.SqlClient + SqlServer 2005+
FreeSql.Provider.Sqlite NETStandard2.0、net45、net40
FreeSql.Provider.Oracle NETStandard2.0、net45、net40
FreeSql.Provider.Firebird NETStandard2.0、net452
FreeSql.Provider.MsAccess NETStandard2.0、net45、net40
FreeSql.Provider.Dameng NETStandard2.0、net45、net40 基于 达梦数据库
FreeSql.Provider.ShenTong NETStandard2.0、net45、net40 基于 神州通用数据库
FreeSql.Provider.KingbaseES NETStandard2.0、net461 基于 人大金仓数据库
FreeSql.Provider.Odbc NETStandard2.0、net45、net40 基于 ODBC
FreeSql.Extensions.LazyLoading NETStandard2.0、net45、net40 延时属性扩展包
FreeSql.Extensions.JsonMap NETStandard2.0、net45、net40 Json 序列化扩展包
FreeSql.Extensions.Linq NETStandard2.0、net45、net40 LinqToSql IQueryable 扩展包
FreeSql.Extensions.BaseEntity NETStandard2.0
FreeSql.Generator NETCoreapp3.1 从数据库生成实体类

系列文章导航

Jetbrains系列产品激活文件-小刀娱乐网 - 专注活动,软件,教程分享!总之就是网络那些事。

mikel阅读(1872)

来源: Jetbrains系列产品激活文件-小刀娱乐网 – 专注活动,软件,教程分享!总之就是网络那些事。

软件介绍

JetBrains公司旗下的IntelliJ IDEAJava是广泛使用的编程语言开发撰写时所用的集成开发环境。JetBrains全家桶,包括IntelliJ IDEA , Pycharm , Webstorm , PhpStorm , Rider Clion , RubyMine, AppCode, Goland, DataGrip , Kotlin等15款产品。Jetbrains系列产品2020.2.4最新激活文件,可以永久激活最新版本。

IDEA,专为Java编程语言开发撰写时常用的集成开发环境。

PhpStorm,专为Web和PHP开发者打造的专业集成开发环境。

RubyMine,针对Ruby和Rails的集成开发环境,为使用Ruby语言进行Web开发的开发人员们提供最为必需的工具和舒适便捷的开发环境。

PyCharm,针对Python语言打造的轻量级IDE,通过Django框架和Google App Engine来支持web开发。

AppCode,专为iOS/OS X开发人员打造的智能IDE,协助他们以更高的生产效率使用Objective-C、Swift、C或C++进行苹果设备的应用开发。

CLion,适用于C/C++开发人员的跨平台IDE,提供极佳的编码协助,为您节省大量时间。

WebStorm,适用于专业JavaScript和前端Web开发人员的集成开发环境。

DataGrip,适用于数据库与SQL开发人员的超级利器。

软件截图

1.png

2.png

关于激活

by Neo Peng

zhile.io/2018/08/25/jetbrains-license-server-crack.html

Jetbrains全系列产品2020.2.4及以下版本(理论上适用目前所有新老版本)

最新注册服务器(License Server)的解锁,可用它来激活你的Jetbrains IDE。

支持Activation Code注册码激活(自定义License name)可用于离线环境。

新的agent license server:https://fls.jetbrains-agent.com(HTTP也可用)

Jebrains付费插件Activation code,暂不在列表的试试 license server。

以下IDE版本实测可成功激活:

IntelliJ IDEA 2020.2.4及以下

AppCode 2019.3.7及以下

CLion 2019.3.5及以下

DataGrip 2020.2.4及以下

GoLand 2020.2.4及以下

PhpStorm 2020.2.4及以下

PyCharm 2020.2.4及以下

Rider 2019.3.4及以下

RubyMine 2019.3.4及以下

WebStorm 2020.2.4及以下

2020.11.26 例行更新激活文件配置助手安装参数

本激活文件适用于2020、2019、2018 全系列版

使用说明

Jetbrains系列产品 2020.2.3 激活文件 v3.2.2 2020.10.27 (支持2020.2.3及以下版本)

1.使用说明:之前lib目录有jar文件先删除,启动软件选试用(Evaluate for free)- Evaluate

2.将解锁激活文件 jetbrains-agent-latest.zip 拖到IDE欢迎界面,重启软件,会弹出配置助手

3.将最新安装参数->粘贴->配置助手对话框->点击为产品激活,重启即激活旗舰版至2089年!

JetBrains产品官方简体中文语言包页面(适用 JetBrains所有产品汉化)

要个毛的使用说明,启动软件,选择试用(Evaluate for free)- Evaluate

JetBrains_zh-CNLangPack_EAP.jar汉化插件包拖到IDE欢迎界面重启完事。

https://plugins.jetbrains.com/plugin/13710

教育邮箱或学生证免费1年正版授权官方申请地址

https://sales.jetbrains.com/hc/zh-cn/articles/207154369

创业公司5折正版授权官方购买地址

https://www.jetbrains.com/shop/eform/startup

下载地址:

链接: https://pan.baidu.com/s/1UD8W2C3cLy07MyFcRjR7RQ 提取码: q6ms

ASP.NET MVC同时支持web与webapi模式_Laymat's blog-CSDN博客

mikel阅读(1085)

来源: ASP.NET MVC同时支持web与webapi模式_Laymat’s blog-CSDN博客

我们在创建 web mvc项目时是不支持web api的接口方式访问的,所以我们需要添加额外的组件来支持实现双模式。

首先我们需要准备三个web api依赖的组件(目前在.net 4/4.5版本下面测试正常,2.0暂未进行测试,需要自行测试)

1、Microsoft.AspNet.WebApi.Client.5.2.2

2、Microsoft.AspNet.WebApi.Core.5.2.2

3、Microsoft.AspNet.WebApi.WebHost.5.2.2

web api依赖组件下载地址 https://pan.baidu.com/s/1slJHdVJ

下载依赖组件后解压至packages目录或其他目录即可,解压完毕后打开编辑器 > 添加引用 > 找到下载的三个依赖dll并引用(其中Microsoft.AspNet.WebApi.Client.5.2.2为System.Net.Http的扩展包,故引用只有两个):

(如果原来已经引用了System.Web.Http则需要删除原来的引用后重新引用)

添加完引用后,我们需要在App_Start目录添加一个WebApiConfig的配置文件,用于初始化api访问路由,代码如下:

  1.     public static class WebApiConfig
  2.     {
  3.         public static void Register(HttpConfiguration config)
  4.         {
  5.             // Web API 路由
  6.             config.MapHttpAttributeRoutes();
  7.             config.Routes.MapHttpRoute(
  8.                 name: “DefaultApi”,
  9.                 routeTemplate: “api/{controller}/{id}”,
  10.                 defaults: new { id = RouteParameter.Optional }
  11.             );
  12.         }
  13.     }

添加完该配置文件后,我们接下来就需要在Global.asax全局文件中注册该配置文件:

  1.         protected void Application_Start()
  2.         {
  3.             AreaRegistration.RegisterAllAreas();
  4.             GlobalConfiguration.Configure(WebApiConfig.Register);
  5.             FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
  6.             RouteConfig.RegisterRoutes(RouteTable.Routes);
  7.         }

(注意:GlobalConfiguration.Configure(WebApiConfig.Register);注册信息必须位于普通RouteConfig之前,不然无法生效。)

完成以上配置操作后,我们就对webapi的基本运行环境配置完毕了,接下来我们添加一个web api controller:

  1.     [RoutePrefix(“api/Notify”)]
  2.     public class NotifyController : ApiController
  3.     {
  4.         [Route(“Alipay”)]
  5.         public string Alipay()
  6.         {
  7.             return “success”;
  8.         }
  9.     }

[RoutePrefix(“api/Notify”)] 配置api访问路径  示例:http://domain/api/Notify

[Route(“Alipay”)] 配置api访问接口  示例:http://domain/api/Notify/Alipay

如果需要对某个方法设置访问模式,则可以添加上[HttpPost]或[HttpGet]位于[Route]上方。

Datav超炫酷的可视化,大屏数据展示组件库-dataV组件库,“react-vue -组件库”_@baby张~的博客-CSDN博客

mikel阅读(1996)

来源: Datav超炫酷的可视化,大屏数据展示组件库-dataV组件库,“react-vue -组件库”_@baby张~的博客-CSDN博客

简单demo ,文尾有github demo地址

对可视化有所了解的应该都知道,某云平台的一款datav大屏可视化的工具,作者前年买了个个人版的,直接在页面上拖住就可以了确实很强。最近平台发来邮件,说我的个人版datav到期了,本来想续租,发现之前的个人版下架了,只剩下企业版、专业版、至尊版,最便宜的企业版一年4800,看着这价钱我慌了,够我吃一年小龙虾的了,不香嘛。果断放弃续费。。。

于是乎,就发现了这款github刚出来几个月的组件库。datav

如他所言:开源长期维护, 目前支持vue 、react、npm

所以组件都有vue和react版可以切换,很贴心。

svg绘制的特效,也就避免大屏幕缩放失真的问题

官方效果图


看着还是有点东西的,那么我们来实际操作一波看看。

自己动手撸

创建一个react或者vue项目

npm install @jiaminghi/data-view
或 yarn add @jiaminghi/data-view
  • 1
  • 2

也支持cdn

<!--调试版-->
  <script src="https://unpkg.com/@jiaminghi/data-view/dist/datav.map.vue.js"></script>
<!--压缩版 生产版本-->
  <script src="https://unpkg.com/@jiaminghi/data-view/dist/datav.min.vue.js"></script> 
  • 1
  • 2
  • 3
  • 4

安装完引入我们需要用到的组件,dom里直接使用组件

注意:它有react和vue版本,使用时别忘了切换

import { BorderBox1 ,BorderBox8 ,BorderBox13,Decoration1 ,ScrollBoard,ScrollRankingBoard } from '@jiaminghi/data-view-react'

 <BorderBox8>
  <div className="xpanel">
    <div className="fill-h" id="mainMap3"></div>
  </div>
 </BorderBox8>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

还可以配合其他的图表使用,我这里还用了echarts

最后实现的就是主图展示的

github Demo地址:https://github.com/babybrotherzb/my-datav

实际应用,升级版数据大屏项目

核心:Datav+G2+L7+Canvas+手写翻牌器
在这里插入图片描述
面朝科技-欢迎你的加入:https://www.mianchao.com/

还不懂Docker?一个故事安排的明明白白! - 轩辕之风 - 博客园

mikel阅读(862)

来源: 还不懂Docker?一个故事安排的明明白白! – 轩辕之风 – 博客园

程序员受苦久矣

多年前的一个夜晚,风雨大作,一个名叫Docker的年轻人来到Linux帝国拜见帝国的长老。

“Linux长老,天下程序员苦于应用部署久矣,我要改变这一现状,希望长老你能帮帮我”

长老回答:“哦,小小年纪,口气不小,先请入座,你有何所求,愿闻其详”

Docker坐下后开始侃侃而谈:“当今天下,应用开发、测试、部署,各种库的依赖纷繁复杂,再加上版本之间的差异,经常出现在开发环境运行正常,而到测试环境和线上环境就出问题的现象,程序员们饱受此苦,是时候改变这一状况了。”

Docker回头看了一眼长老接着说到:“我想做一个虚拟的容器,让应用程序们运行其中,将它们需要的依赖环境整体打包,以便在不同机器上移植后,仍然能提供一致的运行环境,彻底将程序员们解放出来!”

Linux长老听闻,微微点头:“年轻人想法不错,不过听你的描述,好像虚拟机就能解决这个问题。将应用和所依赖的环境部署到虚拟机中,然后做个快照,直接部署虚拟机不就可以了吗?”

Docker连连摇头说到:“长老有所不知,虚拟机这家伙笨重如牛,体积又大,动不动就是以G为单位的大小,因为它里面要运行一个完整的操作系统,所以跑起来格外费劲,慢就不说了,还非常占资源,一台机器上跑不了几台虚拟机就把性能拖垮了!而我想要做一个轻量级的虚拟容器只提供一个运行环境,不用运行一个操作系统,所有容器中的系统内核还是和外面的宿主机共用的,这样就可以批量复制很多个容器,轻便又快捷

Linux长老站了起来,来回踱步了几圈,思考片刻之后,忽然拍桌子大声说到:“真是个好想法,这个项目我投了!”

Docker眼里见光,喜上眉梢,“这事还真离不开长老的帮助,要实现我说的目标,对进程的管理隔离都至关重要,还望长老助我一臂之力!”

“你稍等”,Linux长老转身回到内屋。没多久就出来了,手里拿了些什么东西。

“年轻人,回去之后,尽管放手大干,我赐你三个锦囊,若遇难题,可依次拆开,必有大用”

Docker开心的收下了三个锦囊,拜别Linux长老后,冒雨而归。

锦囊1:chroot & pivot_root

受到长老的鼓励,Docker充满了干劲,很快就准备启动他的项目。

作为一个容器,首要任务就是限制容器中进程的活动范围——能访问的文件系统目录。决不能让容器中的进程去肆意访问真实的系统目录,得将他们的活动范围划定到一个指定的区域,不得越雷池半步!

到底该如何限制这些进程的活动区域呢?Docker遇到了第一个难题。

苦思良久未果,Docker终于忍不住拆开了Linux长老送给自己的第一个锦囊,只见上面写了两个函数的名字:chroot & pivot_root

Docker从未使用过这两个函数,于是在Linux帝国四处打听它们的作用。后来得知,通过这两个函数,可以修改进程和系统的根目录到一个新的位置。Docker大喜,长老真是诚不欺我!

有了这两个函数,Docker开始想办法怎么来“伪造”一个文件系统来欺骗容器中的进程。

为了不露出破绽,Docker很聪明,用操作系统镜像文件挂载到容器进程的根目录下,变成容器的rootfs,和真实系统目录一模一样,足可以以假乱真:

$ ls /
bin dev etc home lib lib64 mnt opt proc root run sbin sys tmp usr var

锦囊2:namespace

文件系统的问题总算解决了,但是Docker不敢懈怠,因为在他心里,还有一个大问题一直困扰着他,那就是如何把真实系统所在的世界隐藏起来,别让容器中的进程看到。

比如进程列表、网络设备、用户列表这些,是决不能让容器中的进程知道的,得让他们看到的世界是一个干净如新的系统。

Docker心里清楚,自己虽然叫容器,但这只是表面现象,容器内的进程其实和自己一样,都是运行在宿主操作系统上面的一个个进程,想要遮住这些进程的眼睛,瞒天过海,实在不是什么容易的事情。

Docker想过用HOOK的方式,欺骗进程,但实施起来工作太过复杂,兼容性差,稳定性也得不到保障,思来想去也没想到什么好的主意。

正在一筹莫展之际,Docker又想起了Linux长老送给自己的锦囊,他赶紧拿了出来,打开了第二个锦囊,只见上面写着:namespace

Docker还是不解其中之意,于是又在Linux帝国到处打听什么是namespace。

经过一阵琢磨,Docker总算是明白了,原来这个namespace是帝国提供的一种机制,通过它可以划定一个个的命名空间,然后把进程划分到这些命名空间中。

 

 

 

而每个命名空间都是独立存在的,命名空间里面的进程都无法看到空间之外的进程、用户、网络等等信息。

这不正是Docker想要的吗?真是踏破铁鞋无觅处,得来全不费功夫!

Docker赶紧加班加点,用上了这个namespace,将进程的“视野”锁定在容器规定的范围内,如此一来,容器内的进程彷佛被施上了障眼法,再也看不到外面的世界。

锦囊3:CGroup

文件系统和进程隔离的问题都解决了,Docker心里的石头总算是放下了。心里着急着想测试自己的容器,可又好奇这最后一个锦囊写的是什么,于是打开了第三个锦囊,只见上面写着:CGroup

这又是什么东西?Docker仍然看不懂,不过这一次管不了那么许多了,先运行起来再说。

试着运行了一段时间,一切都在Docker的计划之中,容器中的进程都能正常的运行,都被他构建的虚拟文件系统和隔离出来的系统环境给欺骗了,Docker高兴坏了!

很快,Docker就开始在Linux帝国推广自己的容器技术,结果大受欢迎,收获了无数粉丝,连nginxredis等一众大佬都纷纷入驻。

然而,鲜花与掌声的背后,Docker却不知道自己即将大难临头。

这天,Linux帝国内存管理部的人扣下了Docker准备“处决”掉他,Docker一脸诧异的问到,“到底发生了什么事,为什么要对我下手?”

管理人员厉声说到:“帝国管理的内存快被一个叫Redis的家伙用光了,现在要挑选一些进程来杀掉,不好意思,你中奖了”

 

 

 

Redis?这家伙不是我容器里的进程吗?Docker心中一惊!

“两位大人,我认识帝国的长老,麻烦通融通融,找别人去吧,Redis那家伙,我有办法收拾他”

没想到他还认识帝国长老,管理人员犹豫了一下,就放了Docker到别处去了。

惊魂未定的Docker,思来想去,如果不对容器中的进程加以管束,那简直太危险了!除了内存,还有CPU、硬盘、网络等等资源,如果某个容器进程霸占着CPU不放手,又或者某个容器进程疯狂写硬盘,那迟早得连累到自己身上。看来必须得对这些进程进行管控,防止他们干出出格的事来。

这时候,他想起了Linux长老的第三个锦囊:CGroup!说不定能解这燃眉之急。

经过一番研究,Docker如获至宝,原来这CGroup和namespace类似,也是Linux帝国的一套机制,通过它可以划定一个个的分组,然后限制每个分组能够使用的资源,比如内存的上限值、CPU的使用率、硬盘空间总量等等。系统内核会自动检查和限制这些分组中的进程资源使用量。

 

 

Linux长老这三个锦囊简直太贴心了,一个比一个有用,Docker内心充满了感激。

随后,Docker加上了CGroup技术,加强了对容器中的进程管控,这才松了一口气。

在Linux长老三个锦囊妙计的加持下,Docker可谓风光一时,成为了Linux帝国的大名人。

然而,能力越大,责任越大,让Docker没想到的是,新的挑战还在后面。

阿里大鱼--短信发送API_hit、run-CSDN博客_阿里大鱼短信

mikel阅读(1010)

来源: 阿里大鱼–短信发送API_hit、run-CSDN博客_阿里大鱼短信

参考链接一
参考链接二
项目中运用了阿里大鱼来实现短信的发送.主要步骤如下
1)申请短信签名
在控制台完成模板与签名的申请
2)申请短信模板
在控制台完成模板与签名的申请
3) 第一部分,获取IAcsClient对象,该对象用来发送请求。

//定义常量
final String product = "Dysmsapi";//短信API产品名称(短信产品名固定,无需修改)
final String domain = "dysmsapi.aliyuncs.com";//短信API产品域名(接口地址固定,无需修改)
//替换成你的AK秘钥
final String accessKeyId = "yourAccessKeyId";//你的accessKeyId,参考本文档步骤2
final String accessKeySecret = "yourAccessKeySecret";//你的accessKeySecret,参考本文档步骤2
//设置超时时间-可自行调整
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化ascClient,暂时不支持多region(请勿修改)
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId,
accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);

4)根据短信模板,创建Request请求对象

//组装请求对象
 SendSmsRequest request = new SendSmsRequest();
 //使用post提交
 request.setMethod(MethodType.POST);
 //必填:待发送手机号
 request.setPhoneNumbers("1500000000");
 //必填:短信签名-可在短信控制台中找到
 request.setSignName("云通信");
 //必填:短信模板-可在短信控制台中找到,发送国际/港澳台消息时,请使用国际/港澳台短信模版
 request.setTemplateCode("SMS_1000000");
 //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
 if(content != null){
       request.setTemplateParam(content.toString());
   }
 //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
 request.setOutId("yourOutId");

5)发送短信,获取响应对象

//请求失败这里会抛ClientException异常
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
 if("OK".equals(sendSmsResponse.getCode())){
                return ResultData.ok();
            }else if("isv.MOBILE_COUNT_OVER_LIMIT".equals(sendSmsResponse.getCode()) || 		"isv.BUSINESS_LIMIT_CONTROL".equals(sendSmsResponse.getCode())){ //限流
                return ResultData.fail("短信发送频繁,请稍后再试");
            }

深入浅出之Smarty模板引擎工作机制(二) - 曾是土木人 - 博客园

mikel阅读(1317)

来源: 深入浅出之Smarty模板引擎工作机制(二) – 曾是土木人 – 博客园

源代码下载地址:深入浅出之Smarty模板引擎工作机制
接下来根据以下的Smarty模板引擎原理流程图开发一个自己的模板引擎用于学习,以便加深理解。

 

Smarty模板引擎的原理,其实是这么一个过程:
把模板文件编译成php文件,然后每次都去读取下模板的修改时间,没有修改就不编译。然后include这个“编译”后的PHP文件。
所谓编译也就是模板用正则替换成含PHP代码的过程。
实际上并不会每次请求都编译,所以性能尚可。

模板文件和php程序文件经过模板引擎的编译后合成为一个文件,即编译后的文件。

接下来,我们根据该原理流程写一个简单的模板引擎。。。。。。

  先贴上核心代码:

  Smarty.class.php文件

复制代码
<?php
    class Smarty{
        public $template_dir;//模板目录
        public $compile_dir;//编译目录
        public $arr=array();//定义一个数组,用以存放assign中的第二个参数传过来的值
        public function __construct($template_dir="../templates",$compile_dir="../templates_c"){
                $this->template_dir=$template_dir;//模板目录
                $this->compile_dir=$compile_dir;  //编译目录
            }
        public function assign($content,$replacment=null){
                if($content!=""){                 //如果指定模板变量,才将要赋的值存储到数组中
                        $this->arr[$content]=$replacment;
                    }
            }
        public function display($page){
                $tplFile=$this->template_dir."/".$page;//读取模板文件,注意:如果模板目录下还有子目录,记得要写完整,比如,$smarty->display('Default/index.tpl')
                if(!file_exists($tplFile)){
                        return;
                }
                $comFile=$this->compile_dir."/"."com_".$page.".php";
                $tplContent=$this->con_replace(file_get_contents($tplFile));//将smarty标签替换为php的标签
                file_put_contents($comFile,$tplContent);
                include $comFile;
        }
        public function con_replace($content){
                $pattern=array(
                    '/<{\s*\$([a-zA-Z_][a-zA-Z_0-9]*)\s*}>/i'
                );
                   $replacement=array(
                       '<?php echo $this->arr["${1}"] ?>'
                );
                    return preg_replace($pattern,$replacement,$content);
                }
        }
?>
复制代码

Smarty.class.php代码解释:

  • $template_dir  指定模板文件的目录
  • $compile_dir   指定编译后的模板文件的目录
  • 构造函数

public function __construct($template_dir=”../templates”,$compile_dir=”../templates_c”)

{

$this->template_dir=$template_dir;

$this->compile_dir=$compile_dir;

}

默认情况下,Smarty模板引擎将把templates目录用于存放模板文件,templates_c用于存放编译后的文件

 

  • assign($content,$replacment=null)函数的工作机制是将每次要传递给模板中的变量的值通过语句:$this->arr[$content]=$replacment;保存到数组中。

     那为何要$replacement的值保存到数组中呢?

其实内部操作是这么一个流程:将$replacement值保存到数组—>读取模板文件(index.dwt,由display函数完成)—>将数组中的值匹配给模板文件中的变量(由con_replace()函数完成)—>将替换后的模板文件写入到编译文件中(com_index.dwt.php)—>输出编译后的PHP文件

  • dispaly($page)函数接收一个参数,即要输出的模板文件(index.dwt)
    • 首先,将模板文件的路径赋给$tplFile($tplFile=$this->template_dir.”/”.$page)
    • 判断模板文件是否存在,如果不存在,就没必要加载了,直接return
    • 指定一个编译文件,以便存放替换变量后的模板文件
    • 通过函数file_get_contents()读取模板文件,并通过函数conf_replace()替换掉模板中的smarty标签
    • 将替换变量后的模板文件通过file_put_contents()写入到编译文件中
    • 将编译后的文件include进来,即可输出编译后的文件
  • 函数con_replace($content)用于替换模板文件(index.dwt)中的变量,即将php中的变量值赋给模板中的变量
    • 通过一个可以匹配<{$title}>形式的正则表达式匹配模板文件中的内容,并将匹配到的值替换为<?php echo $title?>的形式
    • 匹配到内容,并将替换后的内容返回
复制代码
/*Smarty.ini.php文件:用于完成初始化smarty的工作*/
<?php
    include "./libs/Smarty.class.php";
    $tpl=new Smarty();
    $tpl->template_dir="./Tpl";    
    $tpl->compile_dir="./Compile";
?>
复制代码

 

复制代码
<!--模板文件-->
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title><{$title}></title>
</head>
<body>
<p>内容:<{$content}></p>
<p>作者:<{$auth}></p>
<p>网址:<{$website}></p>
</body>
</html>
复制代码

 

复制代码
/*index.php文件*/
<?php
    include "./Smarty.ini.php";
    $title="深入浅出之Smarty模板引擎工作机制";    
    $content="Smarty模板引擎工作机制流程图";
    $auth="MarcoFly";
    $website="www.MarcoFly.com";
    $tpl->assign("title",$title);
    $tpl->assign("content",$content);    
    $tpl->assign("auth",$auth);
    $tpl->assign("website",$website);
    $tpl->display("index.dwt");
?>
复制代码

该index.php就是PHP程序员编写的,可以从数据库中获取各种想要的数据,并保存到变量中,然后简单的调用assign()函数将数据保存到数组中,并通过display()函数将编译文件输出

注:此编译文件是php文件,通过服务器端执行,将结果输出的客户端的浏览器上

分析到这里,我们回过头来分析下在深入浅出之Smarty模板引擎工作机制(一)中给出的关于编译后的文件代码:

复制代码
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title><?php echo $this->arr["title"] ?></title>
</head>
<body>
<p>内容:<?php echo $this->arr["content"] ?></p>
<p>作者:<?php echo $this->arr["auth"] ?></p>
<p>网址:<?php echo $this->arr["website"] ?></p>
</body>
</html>
复制代码

由于我们已经通过assign()函数,将要赋给模板标签中变量的值保存到了数组中了,即此时编译后的模板文件,可以直接输出该数组中的值了。

举个例子:

$title="深入浅出之Smarty模板引擎工作机制";
$tpl->assign("title",$title);
当执行了以上两句代码后,在数组$arr中就存放着下标为:title,值为:深入浅出之Smarty模板引擎工作机制的关联数组了。
此时,就可以直接通过$this->arr['title']直接输出该数组的值。
至于对如何从<{$title}>  ---> <?php echo $this->arr['title']?> 的转换,不懂的读者可以再仔细看下con_replace()函数

有了以上几个文件之后,我们在浏览器中访问index.php文件将得到以下结果:

 

到此,我们“开发”了一个自己的模板引擎,并且测试成功,当然,这只是供交流学习之用。如果你觉得这篇文章对你了解smarty模板引擎的工作机制有所帮助的话,请帮忙顶一顶哈O(∩_∩)O~

文章出自:WEB开发_小飞

转载请注明出处:http://www.cnblogs.com/hongfei/archive/2011/12/10/Smarty-two.html