Aspnet Mvc 前后端分离项目手记(二)关于token认证 - 小小爵 - 博客园

mikel阅读(722)

来源: Aspnet Mvc 前后端分离项目手记(二)关于token认证 – 小小爵 – 博客园

在前后端分离的项目中,首先我们要解决的问题就是身份认证

以往的时候,我们使用cookie+session,或者只用cookie来保持会话。

 

一,先来复习一下cookie和session

首先我们来复习一下在aspnet中cookie和session的关系,做一个简单试验

这是一个普通的view没有任何处理

 

 

可以看到,没有任何东西(cookie),然后当我们写入一个session之后

\

 

 

 

会发现多了一个名为ASP.NET_SessionId的cookie。我们都知道在aspnet中,session是保存在服务器端的内存中的,而http协议是无状态的,那么他是怎么确定不同请求的session

没错,session是借助cookie来实现的:cookie中保存着 session的key,当我们清除掉浏览器缓存时,会发现session也找不到了,就是这个原因。

使用session来保持会话有几个很严重的缺点:1 session容易丢失;2无法支持分布式;3,cookie 对跨域的支持不好

 

所以就用到了我们今天说的token

二,token 

1,token的产生

一般是用户登录成功,服务器端产生一个token并返给前端,前端将token保存在cookie或者localStorage里面,然后每次请求时都带上这个token,一般都带在请求头里面

 2,token的内容

一般的token里面必须有的是:1,会话用户的标识:比如userid。2,token的过期时间,如果想更完整一点,可以加上token的颁发者,签名等等

3,token的生成算法,一般是由服务器端将token的主要内容,过期时间等等做非对称加密,然后进行签名算法(防止客户端更改),具体看后面jwt

 

4,token校验

当服务器端收到请求时,首先会校验token,校验有两种不同的方式

 一, token产生后保存在服务器端(redis或者其他比较速度快的缓存中) 。优点:可控性强,可以用这个来做单点登录,比如另一个地方登录,就remove掉之前的token。缺点:实现麻烦一点,而且要占服务器压力

二, token产生后服务器端不保存,只负责校验。 优点:大大降低了服务器的压力,实现起来,也要相对简单一点。缺点:token一旦颁发,服务器端就不可控了,只能等它过期。

    具体用哪种看具体的需求。如果不是做可控性要求很强,个人建议第二种。

 

5 jwt 

jwt 全名Json Web Tokens,算是一种token的规范吧

园子里面有很不不错的介绍 ,比如这篇:阮一峰 jwt介绍   http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

 

组成有三部分

  • Header(头部,一般包含了token的签名方式)
  • Payload(负载,也就是具体的有效部分)
  • Signature(签名,将前两部分进行签名算法,防止客户端篡改)

实现方式,将header部分和payload部分分别进行base64算法,然后用点号“.”隔开拼接,然后进行签名算法,然后在将三部分拼接(点号隔开)就得到了jwt

注意 ,jwt默认是采用base64编码的,也就是说 客户端也能解码得出具体内容的,所以除非特殊情况,重要敏感字段一定不能放在token中

 

以下是具体实现

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace Rk.JWT
{
 
    public class Jwt
    {
        //参考自 阮一峰 jwt介绍  http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
 
        public static string SALT = "OXpcRP8jmCfMKumY";
     
         
        /// <summary>
        ///
        /// </summary>
        /// <param name="ExraPayload">额外的信息</param>
        /// <returns></returns>
        public static string Create(Dictionary<string,object> ExraPayload)
        {
            var Header = new Dictionary<stringstring>();
            Header.Add("tp""MD5");
            var Payload = new Dictionary<stringobject>();
            //JWT 规定了7个官方字段,供选用。
            Payload.Add("iss""signBy"); //颁发人
            Payload.Add("jti", Guid.NewGuid().ToString()); //jwt的id
            Payload.Add("exp",System.DateTime.Now.AddMinutes(20));//过期时间
            Payload.Add("nbf", System.DateTime.Now);//生效时间
            Payload.Add("iat", System.DateTime.Now);//签发时间
            Payload.Add("sub""subject");//主题
            Payload.Add("aud""audience");//受众
            foreach (var item in ExraPayload)
            {
                if (Payload.ContainsKey(item.Key))
                {
                    throw new Exception($"{item.Key}键值已被占用 不能使用 ");
                }
                else
                {
                    Payload.Add(item.Key, item.Value);
                }
            }
            string base64Header = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Header));
            string base64Payload = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Payload));
            string tmp = base64Header + "." + base64Payload;
 
            string sign = Md5(tmp+ SALT);//加盐,重要
            return base64Header+"."+ base64Payload+"."+ sign;
        }
        //校验是否合法,是否过期
        public static bool Check(string token)
        {
            string base64Header = token.Split('.')[0];
            string base64Payload = token.Split('.')[1];
            string sign = token.Split('.')[2];
            string tmp = base64Header + "." + base64Payload;
            var signCheck = Md5(base64Header + "." + base64Payload + SALT);
            if(signCheck!= sign)
            {
                return false;
            }
            var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<stringobject>>(Base64UrlDecode(base64Payload));
            if(  Convert.ToDateTime(dic["exp"])<System.DateTime.Now)
            {
                //过期了
                return false;
            }
            return true;
        }
        //校验是否合法,是否过期
        public static Dictionary<string,object> GetPayLoad(string token)
        {
    
            string base64Payload = token.Split('.')[1];
         
            var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<stringobject>>(Base64UrlDecode(base64Payload));
           
            return dic;
        }
        public static string Base64Url(string input)
        {
            //JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。
            //Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。
            string output = "";
                byte[] bytes = Encoding.UTF8.GetBytes(input);
            try
            {
                output = Convert.ToBase64String(bytes).Replace('+''-').Replace('/''_').TrimEnd('=') ;
             
            }
            catch (Exception e)
            {
                throw e;
            }
            return output;
        }
        public static string Base64UrlDecode(string input)
        {
            string output = "";
         
            input = input.Replace('-''+').Replace('_''/');
            switch (input.Length % 4)
            {
                case 2:
                    input += "==";
                    break;
                case 3:
                    input += "=";
                    break;
            }
            byte[] bytes = Convert.FromBase64String(input);
            try
            {
                output = Encoding.UTF8.GetString(bytes);
            }
            catch
            {
                output = input;
            }
            return output;
        }
        public static string Md5(string input,int bit=16)
        {
            
            MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
            byte[] hashedDataBytes;
            hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(input));
            StringBuilder tmp = new StringBuilder();
            foreach (byte in hashedDataBytes)
            {
                tmp.Append(i.ToString("x2"));
            }
            if (bit == 16)
                return tmp.ToString().Substring(8, 16);
            else
            if (bit == 32) return tmp.ToString();//默认情况
            else return string.Empty;
           
        }
    }
    
}

 

使用方式

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class HomeController : BaseController
   {
       public ActionResult Login(string username, string pwd)
       {
           /// 1, todo 验证用户名密码正确
           //2,//在token中加入用户id,创建token
           var dic = new Dictionary<stringobject>();
           dic.Add("userid""20125521225858");
           string token = JWT.Jwt.Create(dic);
           //验证token是否正确是否过期
           var isChecked = JWT.Jwt.Check(token);
           return Content("");
       }
   }

 

下一篇我们将会聊一聊 rest 风格url在前后端分离项目中的使用

MVC 统一验证Token demo - _小马哥 - 博客园

mikel阅读(547)

来源: MVC 统一验证Token demo – _小马哥 – 博客园

复制代码
/// <summary>
        /// 获取token
        /// </summary>
        /// <param name="staffId"></param>
        /// <returns></returns>
        public JsonResult GetToken(string staffId)
        {
            ResultMsg resultMsg = null;

            //判断参数是否合法
            if (string.IsNullOrEmpty(staffId))
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
                resultMsg.Info = "staffId不合法";
                resultMsg.Data = new Token();
                return Json(resultMsg, JsonRequestBehavior.AllowGet);
            }

            //插入缓存
            Token token = (Token)HttpRuntime.Cache.Get(staffId);
            if (HttpRuntime.Cache.Get(staffId.ToString()) == null)
            {
                token = new Token();
                token.StaffId = staffId;
                token.SignToken = Guid.NewGuid();
                token.ExpireTime = DateTime.Now.AddDays(1);
                HttpRuntime.Cache.Insert(token.StaffId.ToString(), token, null, token.ExpireTime, TimeSpan.Zero);
            }

            //返回token信息
            resultMsg = new ResultMsg();
            resultMsg.StatusCode = (int)StatusCodeEnum.Success;
            resultMsg.Info = "";
            resultMsg.Data = token;
            return  Json(resultMsg, JsonRequestBehavior.AllowGet);

        }
复制代码
复制代码
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Mvc;
using WebApplication_Token.Models;

namespace WebApplication_Token.Controllers
{
    public class VerificationTokenController : Controller
    {
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            ResultMsg resultMsg = null;
            var request = Request;
            var method = request.HttpMethod;
            string staffid = string.Empty, timestamp = string.Empty, nonce = string.Empty, signature = string.Empty;

            if (!string.IsNullOrEmpty(request.Headers["staffid"]))
            {
                staffid = HttpUtility.UrlDecode(request.Headers.GetValues("staffid").FirstOrDefault());
            }
            if (!string.IsNullOrEmpty(request.Headers["timestamp"]))
            {
                timestamp = HttpUtility.UrlDecode(request.Headers.GetValues("timestamp").FirstOrDefault());
            }
            if (!string.IsNullOrEmpty(request.Headers["nonce"]))
            {
                nonce = HttpUtility.UrlDecode(request.Headers.GetValues("nonce").FirstOrDefault());
            }
            if (!string.IsNullOrEmpty(request.Headers["signature"]))
            {
                signature = HttpUtility.UrlDecode(request.Headers.GetValues("signature").FirstOrDefault());
            }

            //GetToken方法不需要进行签名验证
            if (filterContext.ActionDescriptor.ActionName == "GetToken")
            {
                base.OnActionExecuting(filterContext);
                return;
            }

            //判断请求头是否包含以下参数
            if (string.IsNullOrEmpty(staffid) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(signature))
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)HttpStatusCode.PartialContent;
                resultMsg.Info = "请求头缺少参数";
                resultMsg.Data = new Token();
                filterContext.Result = Json(resultMsg, JsonRequestBehavior.AllowGet);//返回json数据
                base.OnActionExecuting(filterContext);
                return;
            }

            //判断token是否有效
            Token token = (Token)HttpRuntime.Cache.Get(staffid);
            
            string signtoken = string.Empty;
            if (token == null)
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
                resultMsg.Info = "token为null";
                resultMsg.Data = new Token();
                filterContext.Result = Json(resultMsg, JsonRequestBehavior.AllowGet);//返回json数据
                base.OnActionExecuting(filterContext);
                return;
            }
            else
            {
                signtoken = token.SignToken.ToString();
            }

            bool timespanvalidate = token.ExpireTime > Convert.ToDateTime(timestamp);
            if (!timespanvalidate)
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)HttpStatusCode.PartialContent;
                resultMsg.Info = "token已过期";
                resultMsg.Data = new Token();
                filterContext.Result = Json(resultMsg, JsonRequestBehavior.AllowGet);//返回json数据
                base.OnActionExecuting(filterContext);
                return;
            }

            //根据请求类型拼接参数
            NameValueCollection coll = Request.Form;
            string[] requestItem = coll.AllKeys;
            Dictionary<string, string> sArray = new Dictionary<string, string>();
            int j = 0;
            for (j = 0; j < requestItem.Length; j++)
            {
                sArray.Add(requestItem[j], Request.Form[requestItem[j]]);
            }
            var queryStr = GetQueryString(sArray);
            var _signature = GetSingnature(timestamp, queryStr.Item1, staffid, signtoken, queryStr.Item2);

            if(signature!= _signature)
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)HttpStatusCode.PartialContent;
                resultMsg.Info = "token不合法";
                resultMsg.Data = new Token();
                filterContext.Result = Json(resultMsg, JsonRequestBehavior.AllowGet);//返回json数据
                base.OnActionExecuting(filterContext);
                return;
            }

        }

        /// <summary>
        /// 获取签名字符串
        /// </summary>
        /// <param name="parames"></param>
        /// <returns></returns>
        public Tuple<string, string> GetQueryString(Dictionary<string, string> parames)
        {
            // 第一步:把字典按Key的字母顺序排序
            IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parames);
            IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();

            // 第二步:把所有参数名和参数值串在一起
            StringBuilder query = new StringBuilder("");//签名字符串
            StringBuilder queryStr = new StringBuilder("");//url参数
            if (parames == null || parames.Count == 0)
            {
                return new Tuple<string, string>("", "");
            }

            while (dem.MoveNext())
            {
                string key = dem.Current.Key;
                string value = dem.Current.Value;
                if (!string.IsNullOrEmpty(key))
                {
                    query.Append(key).Append(value);
                    queryStr.Append("&").Append(key).Append("=").Append(value);
                }
            }

            return new Tuple<string, string>(query.ToString(), queryStr.ToString().Substring(1, queryStr.Length - 1));
        }

        /// <summary>
        /// 根据参数计算签名
        /// </summary>
        /// <param name="timeStamp">发起请求时的时间戳(单位:毫秒)</param>
        /// <param name="nonce">随机数</param>
        /// <param name="staffId">当前请求用户StaffId</param>
        /// <param name="signToken">signToken</param>
        /// <param name="data">参数url</param>
        /// <returns></returns>
        public string GetSingnature(string timeStamp, string nonce, string staffId,string signToken, string data)
        {
            var hash = System.Security.Cryptography.MD5.Create();
            //拼接签名
            var signStr = timeStamp + nonce + staffId + signToken + data;
            //将字符串中字符按升序排序
            var sortStr = string.Concat(signStr.OrderBy(c => c));
            var bytes = Encoding.UTF8.GetBytes(sortStr);
            //使用MD5加密
            var md5Val = hash.ComputeHash(bytes);
            //把二进制转大写十六进制
            StringBuilder result = new StringBuilder();
            foreach (var c in md5Val)
            {
                result.Append(c.ToString("X2"));
            }
            return result.ToString().ToUpper();

        }
    }
}
复制代码

 

从MVC到DDD的架构演进 - 木小丰 - 博客园

mikel阅读(602)

来源: 从MVC到DDD的架构演进 – 木小丰 – 博客园

DDD这几年越来越火,资料也很多,大部分的资料都偏向于理论介绍,有给出的代码与传统MVC的三层架构差异较大,再加上大量的新概念很容易让初学者望而却步。本文从MVC架构角度来讲解如何演进到DDD架构。

从DDD的角度看MVC架构的问题

代码角度:

  • 瘦实体模型:只起到数据类的作用,业务逻辑散落到service,可维护性越来越差;
  • 面向数据库表编程,而非模型编程;
  • 实体类之间的关系是复杂的网状结构,成为大泥球,牵一发而动全身,导致不敢轻易改代码;
  • service类承接的所有的业务逻辑,越来越臃肿,很容易出现几千行的service类;
  • 对外接口直接暴露实体模型,导致不必要开放内部逻辑对外暴露,就算有DTO类一般也是实体类的直接copy;
  • 外部依赖层直接从service层调用,字段转换、异常处理大量充斥在service方法中;

项目管理角度:

  • 交付效率:越来越低;
  • 稳定性差:不好测试,代码改动的影响范围不好预估;
  • 理解成本高:新成员介入成本高,长期会导致模块只有一个人最熟悉,离职成本很大;

第一层:初出茅庐

以上的问题越来越严重,很多人开始把眼光转向DDD,于是埋头啃了几本大部头的书,对以下概念有了基本的了解:

  • 统一语言
  • 限界上下文
  • 领域、子域、支撑域
  • 聚合、实体、值对象
  • 分层:用户接口层、应用层、领域层、基础层

于是把MVC架构进行了改造,演进成DDD的分层架构。

DDD分层架构:

image

image

 

MVC架构到DDD分层架构的映射:

image

image

 

至此,算了基本入门了DDD架构,扩展性也得到了一定的提升。不过随着业务的发展,不断冒出新的问题:

  • 一段业务逻辑代码,到底应该放到应用层还是领域层?
  • 领域服务当成原来的MVC中的service层,随着业务不断发展,类也在不断膨胀,好像还是老样子啊?
  • 聚合包含多个实体类,这个接口用不到这么多实体,为了性能还是直接写个SQL返回必要的操作吧,不过这样貌似又回到了MVC模式
  • 既然实体类可以包含业务逻辑、领域服务也可以放业务逻辑,那到底放哪里?
  • 资料上说领域层不能有外部依赖,要做到100%单测覆盖,可是我的领域服务中需要用到外部接口、中央缓存等等,那这不就有了外部依赖了吗?

第二层:草船借箭(战术设计)

带着问题不断学习他人经验,并不断的尝试,逐渐get到以下技能:

1、领域层

领域(domain)是个模块,包含以下组成部分,传统的service按功能可能拆分到任何一个地方,各司其职。

  • 1个聚合
  • 1到多个实体
  • 若干值对象
  • 多个DomainService
  • 1个Factory:新建聚合
  • 1个Repository:聚合仓储服务
聚合根(AggregateRoot)

聚合本身也是一个实体,聚合可以包含其他实体,其他实体不能脱离聚合而单独提供服务,比如一篇文章下的评论,评论必须从属于文章,没有文章也就没有评论。仓库层(repository)也必须是以聚合为核心提供服务的;

实体:可以理解为一张数据库表,必须有主键;

值对象:没有主键,依附于实体而存在,比如用户实体下住址对象,一般在数据库中已json字符串的形式存在;最常见的值对象是枚举;

仓库服务(repository)

资源库是聚合的仓储机制,外部世界通过资源库,而且只能通过资源库来完成对聚合的访问。资源库以聚合的整体管理对象。因此,一个聚合只能有一个资源库对象,那就是以聚合根命名的资源库。除此之外的其他对象,都不应该提供资源库对象。仓储服务的实现一般有Spring Data JPA、Mybatis两种方式。

如果是用Spring Data JPA实现,直接使用JPA注解@OneToOne、@OneToMany,配合fetch配置,即可一个方法查询出所有的关联实体。

如果是用Mybatis实现,那么repository需要加入多个mapper的引用,再手动做拼装。

这里有一个经典的Hibernate笛卡尔积问题,答案是在聚合根中,一般不会加在大量的关联实体对象。如果确实需要查询关联对象而关联对象又比较多怎么办呢?在DDD中有一个CQRS(Command-Query Responsibility Segregation)模式,是一种读写分离模式,在此场景中需要将查询操作放到查询命令中分页查询。

当然CQRS也是一个很复杂模式,不应照搬他人方案,而是根据自己的业务场景选择适合自己的方案,以下列举了CQRS的几种应用模式:

image

image

 

工厂服务(factory)

作用是创建聚合,只传入必要的参数,工厂服务内部隐藏复杂的创建逻辑。简单的聚合可以直接通过new、静态方法等创建,不是必须由factory创建。

领域服务

单个实体对象能处理的逻辑放到实体里,多个实体或有交互的场景放到领域服务里。

领域服务可不可以调用仓储层或外部接口? 可以,但不能直接和领域服务代码放一起,领域服务模块存放API,实现放基础层(infrastructure)。

领域服务对象不建议直接以聚合名+DomainService命名,而要以操作命令关联,比如用户保存服务命名为:UserSaveService, 审核服务:UserAuditSerivce。

2、应用层

应用层通过应用服务接口来暴露系统的全部功能。在应用服务的实现中,它负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。通过这样一种方式,它隐藏了领域层的复杂性及其内部实现机制。

比如下订单服务的方法:

  1. public void submitOrder(Long orderId) {
  2. Order order = OrderFetchService.fetchById(orderId); //获取订单对象
  3. OrderCheckSerivce.check(order); //验证订单是否有效
  4. OrderSubmitSerivce.submit(order); //提交订单
  5. ShoppingCartClearService.clear(order); //移除购物车中已购商品
  6. NotifySerivce.emailNotify(order.getUser()); //发送邮件通知买家
  7. }

对于复杂的业务来说,应用层也有几种模式:

  • 编排服务:最典型比如Drools;
  • Command、Query命令模式;
  • 业务按Rhase、Step逐层拆分模式;

image

image

 

3、Maven模块划分

基础层是比较简单一层,不过这里还有个比较疑惑的问题:按照DDD的四层架构图去划分Maven模块,基础层是最上的一层,但是基础层也要包含基础组件供其他层使用,这时基础层应该是放到最下层,直接按照这样构建Maven模块会造成循环依赖。

image

image

 

相比来说,另一个架构图更准确一些,不过依然没有直观体现Maven模块如何划分。

image

image

 

我的最佳实践是将基础层拆分两部分,一部分是基础的组件+仓储API,一部分是实现,maven模块划分图如下所示:

image

image

 

第三层:运筹帷幄(战略设计)

经过以上的两层的磨炼,恭喜你把DDD战术都学习完了,应付日常的代码开发也够了,不过作为架构师来说,探索的道路还不能止步于此,接下来会DDD战略部分。战略部分关注点有3个:

  • 统一语言
  • 领域
  • 限界上下文
1、统一语言

统一语言的重要性可以根据Jeff Patton 在《用户故事地图》中给出的一副漫画来直观的描述:

image

image

 

统一语言是提炼领域知识的输出结果,也是进行后续需求迭代及重构的基础,统一语言的建立有以下几个要点:

  • 统一语言必须以文档的形式提供出来,并且在整个项目组的各团队达成共识;
  • 统一语言必须每个中文名有对应的英文名,并且在整个技术栈保持一致;
  • 统一语言必须是完整的,包含以下要素:
    1. 领域模型的概念与逻辑;
    2. 界限上下文(Bounded Context);
    3. 系统隐喻;
    4. 职责的分层;
    5. 模式(patterns)与惯用法。
2、领域划分

以事件风暴的形式(Event Storming),列出所有的用户故事(Use Story),用户故事可通过6W模型来构建,即描写场景的 Who、What、Why、Where、When 与 hoW 六个要素。然后圈选功能相近的部分,就形成了领域,领域又根据职能不同划分为:核心域、支撑域、通用域,

具体的过程有很多参考资料,这里不再细讲,最终的输出是领域划分图,以下是一个保险业务示例:

image

image

 

3、限界上下文

限界上下文包含两部分:上下文(Context)是业务目标,限界(Bounded)则是保护和隔离上下文的边界。

比如上图中的实现部分即是限界上下文的边界,虚线部分代表了领域的边界。限界上下文没有统一的划分标准,需要的读者根据自己的业务场景来甄别如何划分。

一个上下文中包含了相同的领域知识,角色在上下文中完成动作目标;

边界体现在以下几方面:

  • 领域逻辑层:确定了领域模型的业务边界,维护了模型的完整性与一致性,从而降低系统的业务复杂度;
  • 团队合作层:限界上下文一般也是用户换分团队的依据;
  • 技术实现层:限界上下文可当成是微服务的划分边界;

DDD的不足

DDD架构作为一套先进的方法论,在很多场景能发挥很大价值,但是DDD也不是银弹。高级的架构师把DDD架构当成一种工具,结合其他架构经验一起为业务服务。

DDD的不足有几个方面:

  1. 性能:DDD是基于聚合来组织代码,对于高性能场景下,加载聚合中大量的无用字段会严重影响性能,比如报表场景中,直接写SQL会更简单直接;
  2. 事务:DDD中的事务被限定在限界上下文中,跨多个限界上下文的场景需要开发者额外考虑分布式事务问题;
  3. 难度系数高,推广成本大:DDD项目需要领域专家专家,且需要特别熟悉业务、建模、OOP,对于管理者来说评估一个人是否真的能胜任也是一件困难的事情;

总结

本文从MVC架构开始讲述了如何从演进到DDD架构,限于篇幅很多DDD的知识点没有讲到,希望大家在实践过程中能灵活运用,尽享DDD给业务带来的价值。本文如有不足之处敬请反馈。

本文链接:从MVC到DDD的架构演进

作者简介:木小丰,美团Java技术专家,专注分享软件研发实践、架构思考。欢迎关注公共号:Java研发

mysql日期加一个天数获得新的日期 - php、凯 - 博客园

mikel阅读(1228)

来源: mysql日期加一个天数获得新的日期 – php、凯 – 博客园

2012-06-13 20:16 mySQL日期加一个天数获得新的日期

阅读更多

在当前的日期上加三天,天数随便改:

SELECT date_add(CURRENT_DATE(), interval 3 day);

在指定的日期上加三天:
SELECT date_add(‘2014-04-17’, interval 3 day);

在指定的具体时间上加一个时间:

如在”2012年1月1日两点”加上一个小时零十分零十秒的MySQL语句为:

select date_add(‘2014-04-17 2:00:00’, interval ‘1:10:10’ hour_second);

select date_add(日期, interval 1 day);
select date_add(日期, interval 1 hour);
select date_add(日期, interval 1 minute);
select date_add(日期, interval 1 second);
select date_add(日期, interval 1 microsecond);
select date_add(日期, interval 1 week);
select date_add(日期, interval 1 month);
select date_add(日期, interval 1 quarter);
select date_add(日期, interval 1 year);

ThinkPHP vendor 方法导入第三方类库 - 建铭博客 - 博客园

mikel阅读(542)

来源: ThinkPHP vendor 方法导入第三方类库 – 建铭博客 – 博客园

第三方类库

第三方类库指除了 ThinkPHP 框架、应用项目类库之外的其他类库,一般由第三方系统或产品提供,如 Smarty、Zend 等系统的类库等。

前面使用自动加载或 import 方法导入的类库,ThinkPHP 约定是以 .class.php 为后缀的,非这类的后缀,需要通过 import 的参数来控制。

但对第三类库,由于不会有此约定,其后缀只能认为是 php 。为了方便的引入其他框架和系统的类库,ThinkPHP 特意提供了导入第三方类库的功能。第三方类库统一放置在 ThinkPHP系统目录/Vendor 下面,并且使用 vendor 方法导入。

vendor 方法

语法:

boolen vendor(class, baseUrl, ext)

参数说明:

参数    说明
class    必须,表示要导入的类库,采用命名空间的方式。
baseUrl    可选,表示导入的基础路径,省略的话系统采用 ThinkPHP系统目录/Vendor 目录。
ext    可选,表示导入的类库后缀,默认是 .php 。

与 import 方法的区别在于,vendor 方法默认的导入路径为 ThinkPHP系统目录/Vendor 目录,默认后缀为 .php 。

个人经验分享:

当我们想在ThinkPHP中引入第三方扩展,而第三方扩展又没有按照ThinkPHP的规范在编写的时候时,就需要将第三方扩展放置到Library/Vendor目录下,当然,这是针对ThinkPHP 3.2而言,低版本则根据情况来看了。

然后需要在Controller或function中使用第三方扩展时,就可以直接使用vendor()方法来进行引用了。

第三方类库目录结构:

在function函数中使用:

第一种方法:

Vendor('Phpqrcode.phpqrcode');
复制代码
复制代码
/**
 * 生成二维码
 * @param  string  $url  url连接
 * @param  integer $size 尺寸 纯数字
 */
function qrcode($url,$size=4){
    Vendor('Phpqrcode.phpqrcode');
    if (strpos($url, 'http')===false) {
        $url='http://'.$url;
    }
    QRcode::png($url,false,QR_ECLEVEL_L,$size,2,false,0xFFFFFF,0x000000);
}
复制代码
复制代码

第二种方法:

 require './ThinkPHP/Library/Org/Nx/class.phpmailer.php';
 require './ThinkPHP/Library/Org/Nx/class.smtp.php';
复制代码
复制代码
/**
 * 发送邮件
 * @param  string $address 需要发送的邮箱地址 发送给多个地址需要写成数组形式
 * @param  string $subject 标题
 * @param  string $content 内容
 * @return boolean       是否成功
 */
function send_email($address,$subject,$content){
    $email_smtp=C('EMAIL_SMTP');
    $email_username=C('EMAIL_USERNAME');
    $email_password=C('EMAIL_PASSWORD');
    $email_from_name=C('EMAIL_FROM_NAME');
    if(empty($email_smtp) || empty($email_username) || empty($email_password) || empty($email_from_name)){
        return array("error"=>1,"message"=>'邮箱配置不完整');
    }
    require './ThinkPHP/Library/Org/Nx/class.phpmailer.php';
    require './ThinkPHP/Library/Org/Nx/class.smtp.php';
    $phpmailer=new \Phpmailer();
    // 设置PHPMailer使用SMTP服务器发送Email
    $phpmailer->IsSMTP();
    // 设置为html格式
    $phpmailer->IsHTML(true);
    // 设置邮件的字符编码'
    $phpmailer->CharSet='UTF-8';
    // 设置SMTP服务器。
    $phpmailer->Host=$email_smtp;
    // 设置为"需要验证"
    $phpmailer->SMTPAuth=true;
    // 设置用户名
    $phpmailer->Username=$email_username;
    // 设置密码
    $phpmailer->Password=$email_password;
    // 设置邮件头的From字段。
    $phpmailer->From=$email_username;
    // 设置发件人名字
    $phpmailer->FromName=$email_from_name;
    // 添加收件人地址,可以多次使用来添加多个收件人
    if(is_array($address)){
        foreach($address as $addressv){
            $phpmailer->AddAddress($addressv);
        }
    }else{
        $phpmailer->AddAddress($address);
    }
    // 设置邮件标题
    $phpmailer->Subject=$subject;
    // 设置邮件正文
    $phpmailer->Body=$content;
    // 发送邮件。
    if(!$phpmailer->Send()) {
        $phpmailererror=$phpmailer->ErrorInfo;
        return array("error"=>1,"message"=>$phpmailererror);
    }else{
        return array("error"=>0);
    }
}
复制代码
复制代码

第三种方法:

支付宝类库目录结构

vendor('Alipay.AlipaySubmit','','.class.php');

注意说明:Vendor加载的默认后缀是.php的

参数一:必须,表示要导入的类库,采用命名空间的方式

参数二:可选,表示导入的基础路径,省略的话系统采用 ThinkPHP系统目录/Vendor 目录。

参数三:可选,表示导入的类库后缀,默认是 .php 。

支付宝第三方案例代码:

复制代码
复制代码
/**
 * 跳向支付宝付款
 * @param  array $order 订单数据 必须包含 out_trade_no(订单号)、price(订单金额)、subject(商品名称标题)
 */
function alipay($order){
    vendor('Alipay.AlipaySubmit','','.class.php');
    // 获取配置
    $config=C('ALIPAY_CONFIG');
    $data=array(
        "_input_charset" => $config['input_charset'], // 编码格式
        "logistics_fee" => "0.00", // 物流费用
        "logistics_payment" => "SELLER_PAY", // 物流支付方式SELLER_PAY(卖家承担运费)、BUYER_PAY(买家承担运费)
        "logistics_type" => "EXPRESS", // 物流类型EXPRESS(快递)、POST(平邮)、EMS(EMS)
        "notify_url" => $config['notify_url'], // 异步接收支付状态通知的链接
        "out_trade_no" => $order['out_trade_no'], // 订单号
        "partner" => $config['partner'], // partner 从支付宝商户版个人中心获取
        "payment_type" => "1", // 支付类型对应请求时的 payment_type 参数,原样返回。固定设置为1即可
        "price" => $order['price'], // 订单价格单位为元
        // "price" => 0.01, // // 调价用于测试
        "quantity" => "1", // price、quantity 能代替 total_fee。 即存在 total_fee,就不能存在 price 和 quantity;存在 price、quantity, 就不能存在 total_fee。 (没绕明白;好吧;那无视这个参数即可)
        "receive_address" => '1', // 收货人地址 即时到账方式无视此参数即可
        "receive_mobile" => '1', // 收货人手机号码 即时到账方式无视即可
        "receive_name" => '1', // 收货人姓名 即时到账方式无视即可
        "receive_zip" => '1', // 收货人邮编 即时到账方式无视即可
        "return_url" => $config['return_url'], // 页面跳转 同步通知 页面路径 支付宝处理完请求后,当前页面自 动跳转到商户网站里指定页面的 http 路径。
        "seller_email" => $config['seller_email'], // email 从支付宝商户版个人中心获取
        "service" => "create_direct_pay_by_user", // 接口名称 固定设置为create_direct_pay_by_user
        "show_url" => $config['show_url'], // 商品展示网址,收银台页面上,商品展示的超链接。
        "subject" => $order['subject'] // 商品名称商品的标题/交易标题/订单标 题/订单关键字等
    );
    $alipay=new \AlipaySubmit($config);
    $new=$alipay->buildRequestPara($data);
    $go_pay=$alipay->buildRequestForm($new, 'get','支付');
    echo $go_pay;
}
复制代码
复制代码

 

不过当我将PHPMailer放在Vendor目录下后,在本机运行得好好得,最近将程序上传到服务器上时,直接提示Class ‘PHPMailer’ not found然后又在本机运行,还是正确!通过前面这片博客可以知道,我是通过vendor('PHPMailer.class#PHPMailer');

这行代码将PHPMailer引入的。既然提示找不到PHPMailer类,说明没有被正确引入。这是为什么呢?

就粗略看了一下vendor()方法的源码,这才发现其实vendor()方法也就是对import()方法进行了一次参数组装,然后还是交给了import()方法处理。查看import()方法的源码又发现,在import()方法中,对于上面传入参数的解析其实就是将’.’替换成’/’,将’#’替换成了’.’,baseurl则由vendor()方法自动补充上了,指向Vendor目录。所以上面vendor()方法中的参数最终还是被解析成了如下目录:

Library/Vendor/PHPMailer/class.PHPMailer.php

而PHPMailer的入口文件的实际目录地址为:

Library/Vendor/phpmailer/class.phpmailer.php

内容都一样的嘛!不过我用的是Linux的服务器,所以对大小写是严格区分的,这样当然不能成功导入这个类。而解决办法就是将vendor()引入改为:

vendor(‘phpmailer.class#phpmailer’)

另外对于PHPMailer使用时还要注意一点,PHPMailer如果使用SMTP方式发送邮件,需要PHP对fsockopen的支持,所以我们需要修改php.ini中disable_functions中将fscokopen删除,否则会出现运行错误:

fsockopen() has been disabled

通过PHPMailer的ErrorInfo属性可以获取到!

Thinkphp3.2.3整合phpqrcode生成二维码_一颗程序猿-CSDN博客_tp3.2生成二维码

mikel阅读(601)

来源: Thinkphp3.2.3整合phpqrcode生成二维码_一颗程序猿-CSDN博客_tp3.2生成二维码

一、下载phpqrcode
下载地址:https://sourceforge.net/projects/phpqrcode/
下载后解压放到ThinkPHP\Library\Vendor

二、使用
1、调用phpqrcode生成二维码

public function qrcode()
{
$url=”http://www.baidu.com”;
$path = “Public/Uploads/”; //本地文件存储路径
$level=3;
$size=4;
Vendor(‘phpqrcode’);
$errorCorrectionLevel =intval($level) ;//容错级别
$matrixPointSize = intval($size);//生成图片大小 //生成二维码图片
$object = new \QRcode();
$QR =”Public/Uploads/22.png”;
$object->png($url, false, $errorCorrectionLevel, $matrixPointSize, 2);
}

效果

2、生成带logo的二维码
控制器方法

/**
* 生成带背景的二维码图
*/
public function index(){
$productid = I(‘productid’);
$uid = I(‘uid’);
Vendor(‘phpqrcode’);
$value = ‘http://www.baidu.com”; //二维码内容
$errorCorrectionLevel = ‘L’;//容错级别
$matrixPointSize = 6;//生成图片大小 //生成二维码图片
$object = new \QRcode();
$codeurl = ‘code’.$uid;
$object->png($value, ‘Public/’.$codeurl.’.png’, $errorCorrectionLevel, $matrixPointSize, 2);
$logo = ‘Public/’.$codeurl.’.png’;//已经生成的原始二维码图
$QR = ‘Public/Uploads/back.png’;//准备好的logo图片
if ($logo !== FALSE) {
$QR = imagecreatefromstring(file_get_contents($QR));
$logo = imagecreatefromstring(file_get_contents($logo));
$QR_width = imagesx($QR);//二维码图片宽度
$QR_height = imagesy($QR);//二维码图片高度
$logo_width = imagesx($logo);//logo图片宽度
$logo_height = imagesy($logo);//logo图片高度
$logo_qr_width = $QR_width / 5;
$scale = $logo_width/$logo_qr_width;
$logo_qr_height = $logo_height/$scale;
$from_width = ($QR_width – $logo_qr_width) / 2; //重新组合图片并调整大小
imagecopyresampled($QR, $logo, 6, 720, 0, 0, $logo_qr_width, $logo_qr_height, $logo_width, $logo_height);
} //输出图片
$spreadimg = ‘spread’.$uid;//输出图片名字
imagepng($QR, ‘Public/’.$spreadimg.’.png’);
$this->assign(‘spreadimg’,$spreadimg);
$this->display();
}

页面显示

<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title></title>
</head>
<body>
<img src=”__PUBLIC__/{$spreadimg}.png” alt=”” style=”width:100%;height:auto”/>
</body>
</html>

效果,二维码在左下角
————————————————
版权声明:本文为CSDN博主「林猛男」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/longgeaisisi/article/details/86441481

PHP生成二维码就是这么简单,非常easy! - 掘金

mikel阅读(661)

来源: PHP生成二维码就是这么简单,非常easy! – 掘金

一、下载phpqrcode扩展库

官方下载地址:sourceforge.net/projects/ph…

二、使用phpqrcode扩展库

1、解压后,打开如下图:

2、为了方便调用,我们可以修改phpqrcode.php这个文件名,修改成“QRcode.php”,然后添加命名空间,如下:

3、把phpqrcode文件夹放到extend扩展目录

4、在代码里调用

//引用
use phpqrcode\QRcode;
//调用类库静态方法
$qrcode=QRcode::png('二维码内容',false, '容错级别', '图片大小', '外边距离(白边)	');
复制代码

5、示例

<?php
namespace app\index\controller;
use think\Controller;
use phpqrcode\QRcode;

class Qr extends Controller
{
	/**
     * 生成二维码接口
     */
	public function api(){
		$data=input('');
		!isset($data['text']) && $this->error('参数非法');
		$text  = trim($data['text']); 
		//计算图片尺寸
		$width = isset($data['width']) ? trim($data['width']):100;	
		$size  = floor($width/37*100)/100 + 0.01;
		
		$errorCorrectionLevel =intval(2) ;//容错级别 
      	$matrixPointSize = intval($size); //生成图片大小 
		$margin =0;//外边距离(白边)		
		$qrcode=QRcode::png($text,false, $errorCorrectionLevel, $matrixPointSize, $margin);
		die;
	}
}
?>
复制代码

 

作者:元歌
链接:https://juejin.cn/post/6986282985829957669
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

thinkphp:html中的if condition 嵌套写法、条件判断的各种情况(eq、neq、gt、lt、or、and) - 简书

mikel阅读(622)

gt

来源: thinkphp:html中的if condition 嵌套写法、条件判断的各种情况(eq、neq、gt、lt、or、and) – 简书

伪代码 术语符号
大于 gt
小于 lt
等于 eq
不等于 neq
或者 or
并且 and

变量start_time代表的含义是活动的开始时间,变量end_time代表的含义是活动的结束时间。两个变量都是时间戳的格式。下面就将显示状态一列的数据,各种情况下的判断条件列举出来。


等于 eq

<td>
      <if condition="$vo['start_time'] eq $vo['end_time'] ">进行中
        <else>已结束</else>
        </if>
</td>
image.png

不等于 neq

<td>
      <if condition="$vo['start_time'] neq $vo['end_time'] ">进行中
        <else>已结束</else>
        </if>
</td>
image.png

大于 gt

<td>
      <if condition="$vo['start_time'] gt 0 ">进行中
        <else>已结束</else>
        </if>
</td>
image.png

小于 lt

<td>
      <if condition="$vo['start_time'] lt 0 ">进行中
        <else>已结束</else>
        </if>
</td>
image.png

或者 or

<td>
      <if condition="($vo['start_time'] - $time gt 0) OR ($time neq 666) ">
        <else>已结束</else>
        </if>
</td>
image.png

并且 and

<td>
    <if condition="($vo['start_time'] gt 0) AND ($time eq 666) ">进行中
        <else>已结束</else>
        </if>
</td>
image.png

各种if和else的嵌套

超过一个if else的写法

<div align="center">
                    <if condition="$company_data.approval eq 0 ">
                            <!-- 审核中 -->
                            <img id="imgChange"  src="__TMPL__/public/assets/images/step2.png" alt="">
                    <elseif condition="$company_data.approval eq 1 ">
                            <!-- 审核通过 -->
                            <img id="imgChange"  src="__TMPL__/public/assets/images/step3.png" alt="">
                    <elseif condition="($company_data.approval eq 2) OR ($company_data.approval eq 99) ">
                            <!-- 审核被拒或者未认证 -->
                            <img id="imgChange"  src="__TMPL__/public/assets/images/step1.png" alt="">
                    <else />
                            <!--  其余情况 -->
                            <img id="imgChange"  src="__TMPL__/public/assets/images/step1.png" alt="">  
                   </if>         
                </div>

一个if else的写法

<if condition="$company_data.approval eq 0 ">
                <div align="center" id="step2ID" class="step2_class" style="">
                    <label class="step2Class" style="color:Red;font-size:30px;" id="step2Label">人工审核中,请等待1-2天      
                    </label>
                </div>
          <else>
                 <div align="center" id="step2ID" class="step2_class" style="display:none;">
                     <label class="step2Class" style="color:Red;font-size:30px;" id="step2Label">人工审核中,请等待1-2天  
                     </label>
                 </div>
           </else>

</if>

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

Flutter 项目实战 网络请求MD5+时间戳+验证签名 十一_我很好-CSDN博客

mikel阅读(698)

来源: Flutter 项目实战 网络请求MD5+时间戳+验证签名 十一_我很好-CSDN博客

/ MD5 ( Message-Digest Algorithm ) /
MD5消息摘要算法 , 由MD4、MD3、MD2 改进而来,主要增强算法复杂度和不可逆性 , 密码散列函数 。可以生成一个128位(16个字符(BYTES))的散列值(hash value), 128位的MD5散列被表示为32位十六进制数字 , 确保信息完整一致传输 。可以被破解 , 证实MD5算法无法防止碰撞攻击,因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。对于高度安全性的资料 可以使用 SHA-2 算法 。

MD5在线加密 / MD5免费解密 / MD5付费解密

/ MD5验证签名 /
密钥可以是任意字符串 , 为了增加破解难度 ,客户端和服务端协商的密钥字符串长度尽量要长 。密钥分别在客户端和服务端保存了一份 。客户端获取时间戳、签名和其他必备参数添加到Map集合中 , 然后对Map集合按Key的首字母顺序排列 , 最好遍历Map集合分别获取key和value循环拼接到赋值给一个字符串变量 。通过遍历集合拼接好的字符串末尾拼接协商的密钥 , 将这个拼接好的字符串通过Md5算法加密 。

 

/ 时间戳防止重复提交 /
Redis (REmote DIctionary Server) 是 key-value 存储系统,是跨平台的非关系型数据库。开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库 。Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。

获取上次存储到redius的时间戳和客户端发起请求到时间戳进行对比 , 以此来校验时间戳是否重复 、session是否超时 。

 

/ Map集合按Key的首字母排序 /
DartPad

排序算法

排序后

/ MD5加密 /
获取 Android 平台密钥签名
打开 Flutter工程 下Android文件 配置密钥 到 gradle.properties (Project Properties)

 

 

build.gradle 下配置 flutterMd5Key (对应的别名 : FLUTTER_MD5_KEY)

 

Android Studio 菜单栏通过 build -> make project 编译项目后在BuildConfig文件下生成 静态的不可变的密钥 FLUTTER_MD5_KEY 。

 

Flutter 与 Android原生建立通信 ,获取密钥 (FLUTTER_MD5_KEY)

 

 

在main函数调用里面需要调用 函数 ensuerInitialized (WidgetFlutterBinding用于与 Flutter 引擎进行交互 ) , Flutter获取Android原生交换返回的密钥 FLUTTER_MD5_KEY 添加到 参与签名的Map集合中 , 最后遍历集合拼接签名参数进行Md5加密 。

WidgetsFlutterBinding.ensureInitialized();
pubspec.yaml 文件下配置 common_utils 依赖库 (包含了md5加密的工具类)

 

 

import ‘package:common_utils/common_utils.dart’;
import ‘package:flutter/cupertino.dart’;
import ‘package:flutter/services.dart’;

void main() async {
///时间戳
var _milSec = (DateTime.now().millisecondsSinceEpoch / 100).toStringAsFixed(0);
/// Map 集合
Map<String, dynamic> encryMap = {
‘g_key’: ‘g_value’,
‘b_key’: ‘b_value’,
‘e_key’: ‘e_value’,
‘a_key’: ‘a_value’,
‘mil_sec’:’$_milSec’
};

/// Map 集合的键存储到 List 集合
List<String> keys = encryMap.keys.toList();

/// Map 集合按照 key 排序
keys.sort((a, b) {
List<int> al = a.codeUnits;
List<int> bl = b.codeUnits;
for (int i = 0; i < al.length; i++) {
if (bl.length <= i) return 1;
if (al[i] > bl[i]) {
return 1;
} else if (al[i] < bl[i]) return -1;
}
return 0;
});
print(‘排序后的key $keys \n’);
var _signStr = ”;
keys.asMap().forEach((k, v) {
encryMap.forEach((pk, pv) {
if (v == pk) {
print(‘键集合keys索引$k 集合encryMap键$pk 集合encryMap值$pv’);
_signStr += pk + “=” + ‘${pv ?? ”}’ + “&”;
}
});
});
WidgetsFlutterBinding.ensureInitialized();
var _flutterNativeKeyValue = await FlutterPlugins.senData(‘flutterSendNavData’);
_signStr = _signStr.substring(0, _signStr.length – 1) + _flutterNativeKeyValue;
///拼接好的签名参数
print(‘拼接好的签名参数 $_signStr’);

var _encMdeSignStr=EncryptUtil.encodeMd5(_signStr);
print(‘Md5签名:$_encMdeSignStr’);
}

class FlutterPlugins {
static const MethodChannel channel =
const MethodChannel(“com.flutter.native.key”);

static Future<String> senData(String result) async {
return await channel.invokeMethod(“flutterNativeKey”, result);
}
}

MD5在线加密 对依赖库的签名算法进行验证

 

获取 IOS 平台密钥签名
使用Xcode工具打开Flutter工程里面的IOS文件夹 , 找到 AppDelegate.swift 文件打开 , 配置密钥完成Flutter从IOS获取密钥的过程

 

 

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

var methodChannel:FlutterMethodChannel?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)

///Flutter和IOS通信的唯一标识符 com.flutter.native.key
let vc = self.window.rootViewController as! FlutterViewController
self.methodChannel = FlutterMethodChannel.init(name: “com.flutter.native.key”, binaryMessenger: vc.binaryMessenger)

self.methodChannel!.setMethodCallHandler { (call , result) in
///Flutter调用的函数
if(call.method == “flutterNativeKey”){
///密钥
result(“qazwsxedcrfvtgbyhnujmikolp0987654321”);
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

xcode控制台输出的加密参数

/ 调用so文件 /
在当前工程目录下 用这个命令创建flutter工程 native_add

flutter create –platforms=android,ios –template=plugin native_add

在创建的natvie_add工程里面的 ios 文件夹下 Classes 文件夹里面创建 native_add.cpp 文件

 

 

native_add.cpp文件里面配置密钥

#include <stdint.h>
#include <string.h>

#define DART_API extern “C” __attribute__((visibility(“default”))) __attribute__((used))
DART_API const char *greetString(const char *platform) {
char _platform[]=”android”;
if(strcmp(_platform,platform)==0){
return “androidqazwsxedcrfvtgbyhnujmikolp0987654321”;
}
return “iosdqazwsxedcrfvtgbyhnujmikolp0987654321”;
}
在创建的natvie_add工程里面的 Android 文件夹下 创建 CMakeLists.txt 文件用来定义如何编译源文件 , 添加 externalNativeBuild 到 android/build.gradle

 

 

native_add 工程 pubspec.yaml 文件下配置ffi依赖库 并完成依赖加载

 

native_add.dart 文件里面 导入ffi 依赖 , Android平台加载libnative_add.so库 ,工程编译后会生成.so库文件 (build/native_add/intermediates/cmake/Debug/obj路径下)

 

 

 

运行工程查看从 native_add.cpp 获取到的密钥

 

 

 

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

 Centos宝塔面板清理垃圾空间_Doer的博客-CSDN博客_宝塔怎么清理系统盘垃圾

mikel阅读(1287)

来源: Centos宝塔面板清理垃圾空间_Doer的博客-CSDN博客_宝塔怎么清理系统盘垃圾

宝塔总是用一段时间之后空间被占用非常多,我们明明没有产生什么数据。主要是日志占用了空间,现在我们来清理一下。

第一步、清空回收站
这一步是最简单的,打开回收站清空就行,需要注意的是回收站保留了可能误删的数据,如果重要就不要删了。
如果面板已经打不开了,可以使用指令来清理回收站:
rm -rf /www/Recycle_bin/*

第二步、清理宝塔日志
占用空间最多的主要是日志,宝塔面板的日志可以在宝塔安装【日志清理工具】,扫描之后自动清理。

第三步、清理安全日志
点击宝塔面板的左侧菜单栏【安全】,进去后在右上角可以看到一个
Web日志:/www/wwwlogs284.15 MB [tag type=“success”]清空[/tag]
这样的按钮,点击清空就可以了。

第四步、清理系统日志
CentOS系统中有两个日志服务,分别是传统的 rsyslog 和 systemd-journal
systemd-journald是一个改进型日志管理服务,可以收集来自内核、系统早期启动阶段的日志、系统守护进程在启动和运行中的标准输出和错误信息,还有syslog的日志。

该日志服务仅仅把日志集中保存在单一结构的日志文件/run/log中,由于日志是经历过压缩和格式化的二进制数据,所以在查看和定位的时候很迅速。
经过查找发现/var/log/journal目录下的日志文件占用空间非常大,达到了4G。
可以使用命令du -t 100M /var 或者 journalctl –disk-usage 来查看占用清空
这些日志文件记录了很长时间以来的systemd情况,毫无价值,我们可以使用journalctl工具来清理,具体操作方法如下:

只保留近一周的日志
journalctl –vacuum-time=1w

只保留300M的日志(可以自定义大小)
journalctl –vacuum-size=300M

直接删除 /var/log/journal/ 目录下的日志文件
rm -rf /var/log/journal/f9d400c5e1e8c3a8209e990d887d4ac1

需要注意的是,以上的指令只是暂时清理空间。

journal日志大小永久限制
第四步的方法只是暂时清理,如果需要永久限制大小,需要修改/etc/systemd/journald.conf 配置文件
详细的配置文件说明参考:Linux上 journal 可以删除吗?

永久限制日志大小
打开配置文件sudo vim /etc/systemd/journald.conf,修改参数SystemMaxUse=50M

[tag type=“success”]SystemMaxUse[/tag] 限制全部日志文件总共可以占用多少空间。
修改之后重启生效,重启后日志会自动删减到限制的大小
systemctl restart systemd-journald.service

参考博客:
Linux上 journal 可以删除吗?
Linux /var/log/日志文件太大,清理journal就行
Linux 系统 /var/log/journal/ 垃圾日志清理
————————————————
版权声明:本文为CSDN博主「Mr.Doer」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_38202733/article/details/118894356