2024-10-03 14:48:20
摘要:用户服务开发好之后,本来是要先介绍认证中心的。由于写了几篇了还没有看到电商业务的痕迹,于是今天先介绍品类微服务,做一期业务方面的设计与实现,下回再介绍认证中心。
一、品类与品牌
任何一个电商平台,品类是最先存在的一个业务功能。比如京东它最开始的定位就一个家电行业的垂直电商,后来最大了才有更多的其他品类。而现实中,可能会真正存在一个专门的品类管理处的部门,他们的职责可能是研究公司战略方向的。品类微服务不仅仅只是一个名称,它可能有专门的单独品类首页,有专门的营销策略,商品的存活的、推荐等等,因而品类对于电商业务来说是非常重要的。
品牌决定了电商平台具体卖什么东西,不同的平台品牌同意可能有不同的业务。比如营销方式、合作模式、分成方式等等。
对电商平台访问者来说,他们可能只是搜索商品,选择满意的商品后就下单了。他们知道品类和品牌的存在,但不会考虑到品类和品牌还有那么多具体的后台业务逻辑。而对于电商平台来说,品类和品牌是真正存在的可能还是非常复杂的业务。
接着我们来访问一下京东网站:
可以看到左侧有一个很大的品类选项。展开品类后又有二级或三级选择。随便点击一个三级品类选项后,可以看到URL地址:https://list.jd.com/list.html?cat=652,654,834 cat对应的值就是一个三级分类,分别提供了3个id值。
接着我们再在搜索框上随便搜个结果,可以看到品牌的筛选筛选项,品牌下面还会动态出现一些跟搜索的内容相关参数的筛选项,这些选择项都是动态的。比如我们搜索手机,出现的选择项是CPU、内存,电池,而我们搜索衣服时,出现的选择项变成了颜色、尺码、适用年龄。
根据分析京东的页面,我们大致可以了解到几个信息:
京东的品类有3级分类。
选择具体的品类后,才有关联的品牌。比如选择衣服后才有衣服对应的品牌,选择手机后才有手机的品牌。
筛选项是根据选择的品类后,动态出现的,也就是说筛选项会根据不同的品类有不同的内容,甚至还有高级选项。
于是我们就有了电商平台第一个业务相关的微服务品类微服务:
为什么品类服务要单独出来了,合并到商品服务里面不行吗?
微服务的划分没有标准答案,业务不是很复杂时,可以把品类服务合并到商品服务里面。而我们这里单独出来,是因为电商业务最……
阅读全文
2024-09-28 13:27:01
摘要:一、云原生介绍
概念
什么是云原生:云原生是一种软件开发的方法论,通俗来说就是在云计算环境中构建和运行应用程序
云计算的特点:弹性、可扩展、高可用
云原生关键点:
容器化
微服务
动态管理
CI/CD
弹性(容错机制)
服务网格
容器编排工具
监控平台(指标、链路、日志)
云原生开发与微服务开发
理念(云原生:云计算为基础;传统:固定的硬件)
架构(云原生:微服务、Serverless;传统:单机架构)
开发流程(云原生:CI/CD;传统:瀑布模型(没有自动化))
基础设施(云原生:通过代码管理依赖资源、自动配置;传统:事先准备、手动配置)
服务发现通信(云原生:环境中自带服务发现;传统:Consul、自己实现、提前准备、依赖硬编码、配置文件)
监控和日志(云原生:环境中准备好了各种监控、日志、链路追踪;传统:过于依赖外部组件)
资源利用率(云原生:动态调整资源;传统:资源分配不灵活)
开发环境和生产环境
云原生概要已经提出很久了,k8s可以说就是上就是一个云原生环境。
我在2019年的时候就开始带团队成员来是公司的微服务转型,那个时候都是自己手撸k8s环境,各种中间件集群也是自己搭建的。
那个时候云原生环境确实不好,虽然各大云平台都提供直接的paas产品,但很多收费都较贵,最终还是自己搭建。
更重要的时候本地开发时虽然安装的docker版本,提供了mini k8s环境,但是开发和部署的环境还是不一样的。
二、Asprise介绍
如果能有一个让开发和部署都是云原生的,开发的时候不需要自己去安装mysql,redis;同时开发的时候也不需要知道最终部署的是阿里云,还是微软云。只要开发环境是基于云原生的,开发环境拥有云原生的特性,比如监控、日志、链路追踪。部署的时候只需要提供对应的云产品的产品就能让系统跑起来,这种跑起来的方式,可能就是提供一个连接字符串就可以了。如果有这样的环境,那云原生开发将变得简单很多。
对的,这就是我们当前的明星出场的时候了。
.NET Aspire是NET 8.0 LTS提出的一套开发工具,它的作用就是构建和运行云原生应用程序。
特点:
Dashboard - 中心化的应用监控和探查:F5 启动 .NET Aspire可显示服务的统……
阅读全文
2024-09-14 11:51:22
摘要:上一篇我们介绍了本项目的基础架构,实际上共享项目里面还有很多内容并没有介绍。这是因为在没有具体业务前,光谈抽象类并不是很好理解,因此除了领域模型的规范和通用EFCore的实现外,其他的通用共享类都放到具体的业务中讲。
接下来,本次的出题就开始直接进入业务实现了,首先我们来谈第一个微服务,用户微服务。
一、用户服务领域层
因为我们整个项目采用的是基于洋葱模型的整洁架构,结合DDD的经典分层,我们的微服务基本上都分层四层。
ps:因为是培训项目,实际业务并不一定每个微服务都要采用一样的分层,实际开发中如果是小的,业务相对稳定微服务哪怕直接用文件夹来区分层次也是可以了。
在services文件夹中创建user文件夹,再创建DDM.DHT.UserService.Core类库项目,可以讲项目默认命名空间改成DDM.DHT.UserService。
接着创建Entities文件夹,再创建User.cs类,代码如下:
public class User : BaseAuditEntity, IAggregateRoot
{
protected User() { }
public User(string loginId, string phone)
{
LoginId = loginId;
Phone = phone;
Random random = new Random();
Name = Phone.Substring(0, Phone.Length - 4) + _ + random.Next(100, 999);
UseAble = true;
Salt = loginId.MD5EncodingOnly();
PasswordHash = 123456.MD5EncodingWithSalt(Salt); // default password is 123456
}
public string LoginId { get; private set; } = null!;
public string Name { get; private se……
阅读全文
2024-09-07 22:29:37
摘要: 一直以来想专心写一个基于云原生的微服务案例,但总角色要写的内容太多了。电商案例本身并不会太难,而要搭建好一整套云原生和微服务的基础设施,并不是一项轻松的工作。随着.Net Asprise的推出,再集成Dapr,开发和生产几乎可以保持一样的环境了。这让我惊喜,曾经我们手动搭建k8s,手动配置集群以及可以成为历史,我们需要的就是使用这些工具,重新把核心回归到业务上来。
正好新公司也是做电商业务的,于是我终于决定开始写一套云原生电商微服务的案例。一方面可以学习Asprise和Dapr的相关知识,也是一次属性电商核心业务的机会。
这次的内容会写得比较细,目标是把这个项目当成一个培训项目来写。让不了解云原生、不了解微服务和不了解电商业务的人,也能看得懂该项目。
一、DDD
DDD相关的文章以及很多了,我在几年前也写过一个DDD的学习系列,本文就只是做个总结,毕竟微服务是离不开DDD的。
解决什么问题
问题域
需求分析
分析理解复杂业务领域问题
准确反映业务语言
领域分析概念
领域
子域
核心域、通用域和支撑域
限界上下文
领域建模概念
实体与值对象
聚合与聚合根
领域事件
领域服务
仓储
工作单元模式
规约
应用服务
防腐层
领域驱动设计
CQRS: 命令与查询分离,作为一种战术办法,是实现DDD建模领域的最佳途径之一。
充血模型: 让模型自带业务逻辑,业务属性的改变只有模型自身可以操作。
二、整洁架构
核心原则
独立于框架:整洁架构的系统核心业务逻辑不依赖于具体的软件框架,业务逻辑部分都能够独立运行。这样在框架更新或者替换时,对核心业务的影响最小。
可测试性:架构设计使得业务规则可以很方便地被测试。因为业务逻辑是独立于外部组件(如数据库、用户界面等)的,所以可以使用单元测试来验证业务规则的正确性。比如,在一个电商系统中,“计算商品折扣”的业务规则可以通过提供模拟的商品价格数据来进行单元测试,而不需要真正地连接数据库或者启动整个用户界面。
独立于UI(用户界面):业务逻辑与用户界面相互独立。这意味着可以方便地替换用户界面,比如从一个命令行界面转换为图形界面,或者从Web界面转换为移动应用界面,而不会影响到业务逻辑。
独立于数据库:系统的核心不依赖于数据库的类型……
阅读全文
2021-11-14 11:12:25
摘要:一、基础概念
1. 聚集索引(Clustered Index)
结构特点
数据存储:
聚集索引决定了表中数据的物理存储顺序。
表中的每一行数据都会按照聚集索引的键值进行排序存储。
叶节点:
聚集索引的叶节点包含实际的数据行。
叶节点的数据行是按照聚集索引的键值连续存储的。
唯一性:
每个表只能有一个聚集索引。
聚集索引的键值必须是唯一的,除非在创建时允许重复键值(通过 ALLOW_ROW_LOCKS 和 ALLOW_PAGE_LOCKS 选项)。
存储效率:
由于数据按照键值连续存储,聚集索引在范围查询和排序操作中非常高效。
如果表中有大量数据,聚集索引的维护成本相对较高,因为插入、删除和更新操作需要重新排列数据。
使用场景
范围查询:
适用于需要频繁进行范围查询(如 BETWEEN、、)的表。
排序和分组:
适用于需要频繁进行排序和分组操作的列。
主键:
通常主键会创建为聚集索引,因为主键需要唯一标识每一行数据,并且主键列通常用于范围查询和排序操作。
数据访问模式:
适用于访问模式以顺序访问数据为主的场景。
2. 非聚集索引(Non-Clustered Index)
结构特点
数据存储:
非聚集索引的键值存储的是指向实际数据行的指针。
表中的数据行可以按照插入顺序或其他顺序存储,但非聚集索引提供了一种快速查找数据的方式。
叶节点:
非聚集索引的叶节点包含指向实际数据行的指针。
叶节点的数据行指针是按照非聚集索引的键值排序的。
唯一性:
每个表可以有多个非聚集索引。
非聚集索引的键值可以是唯一的,也可以不唯一。
存储效率:
非聚集索引的维护成本相对较低,因为插入、删除和更新操作不会重新排列实际数据行。
非聚集索引可以提高特定列的查询性能,但对整个表的数据存储没有影响。
使用场景
等值查询:
适用于需要频繁进行等值查询(如 =、IN)的列。
范围查询:
虽然非聚集索引也可以用于范围查询,但效率可能不如聚集索引,尤其是在范围较大时。
排序和分组:
适用于需要频繁进行排序和分组操作的列……
阅读全文
2019-11-16 15:35:24
摘要:影响性能因素
* 数据库结构设计
* T-SQL语句
* 数据量大
* 事务和隔离级别
* 硬件资源
* IO阻塞
* 批量删除表数据:大量删除时会记录到日志中,也会造成IO阻塞
阅读全文
2018-11-16 16:46:20
摘要:需求背景
假设,你正在参与开发一个微服务。微服务通过 HTTP 协议暴露接口给其他系统调用,说直白点就是,其他系统通过 URL 来调用微服务的接口。有一天,你的 leader 找到你说,“为了保证接口调用的安全性,我们希望设计实现一个接口调用鉴权功能,只有经过认证之后的系统才能调用我们的接口,没有认证过的系统调用我们的接口会被拒绝。我希望由你来负责这个任务的开发,争取尽快上线。”
需求分析
1. 第一轮基础分析
对于如何做鉴权这样一个问题,最简单的解决方案就是,通过用户名加密码来做认证。我们给每个允许访问我们服务的调用方,派发一个应用名(或者叫应用 ID、AppID)和一个对应的密码(或者叫秘钥)。调用方每次进行接口请求的时候,都携带自己的 AppID 和密码。微服务在接收到接口调用请求之后,会解析出 AppID 和密码,跟存储在微服务端的 AppID 和密码进行比对。如果一致,说明认证成功,则允许接口调用请求;否则,就拒绝接口调用请求。
2. 第二轮分析优化
不过,这样的验证方式,每次都要明文传输密码。密码很容易被截获,是不安全的。那如果我们借助加密算法(比如 SHA),对密码进行加密之后,再传递到微服务端验证,是不是就可以了呢?实际上,这样也是不安全的,因为加密之后的密码及 AppID,照样可以被未认证系统(或者说黑客)截获,未认证系统可以携带这个加密之后的密码以及对应的 AppID,伪装成已认证系统来访问我们的接口。 这就是典型的“重放攻击” 。
提出问题,然后再解决问题,是一个非常好的迭代优化方法。对于这个问题,我们可以借助 OAuth 的验证思路来解决。调用方将请求接口的 URL 跟 AppID、密码拼接在一起,然后进行加密,生成一个 token。调用方在进行接口请求的的时候,将这个 token 及 AppID,随 URL 一块传递给微服务端。微服务端接收到这些数据之后,根据 AppID 从数据库中取出对应的密码,并通过同样的 token 生成算法,生成另外一个 token。用这个新生成的 token 跟调用方传递过来的 token 对比。如果一致,则允许接口调用请求;否则,就拒绝接口调用请求。
3. 第三轮分析优化
不过,这样的设计仍然存在重放攻击的风险,还是不够安全。每个 URL 拼接上 AppID、密码生成的 token 都是固定的。未认证系统截……
阅读全文
2018-08-21 22:37:30
摘要:1. 目标
如下代码:我们要实现缓存,但希望让使用者不用关心缓存的具体实现,只需要使用者在要操作缓存的方法上加上特性标注即可。
[Caching(CachingMethod.Remove, GetLinksQuery)]
public class CreateLinkCommand
{
}
[Caching(CachingMethod.Get)]
public class GetLinksQuery : IRequestListLinkViewModel
{
}
要实现我们的目标,我们把任务分成2部分,首先实现缓存逻辑,然后将缓存基于特性做AOP实现。
2. 缓存实现
首先我们定义一个缓存接口
public interface ICacheProvider
{
/// summary
/// 向缓存中添加一个对象。
/// /summary
/// param name=key缓存的键值,该值通常是使用缓存机制的方法的名称。/param
/// param name=valKey缓存值的键值,该值通常是由使用缓存机制的方法的参数值所产生。/param
/// param name=value需要缓存的对象。/param
void Add(string key, string valKey, object value);
void Put(string key, string valKey, object value);
object Get(string key, string valKey);
void Remove(string key);
bool Exists(string key);
bool Exists(string key, string valKey);
}
如上代码,为什么接口中key和valKey2个参数呢?这是因为我们可能会缓存同一个方法不同参数的结果,如在一个获取分页结果的方法中,我们可能会返回不同页的结果。如我们目标中GetLinksQuery方法缓存的值会是一个分页显示结果的字典。key是我们缓存的方法名,valKey这是缓存的字典结果中的字典key,value则是字典的结果。要进一步理解可以查看下面基于内存的……
阅读全文
2008-05-08 12:28:02
摘要:源码
/* ******************************************************************* */
/* CSS FUNCTIONS */
/* ******************************************************************* */
var CSS = (function() {
var css = {};
// 转换的RGB字符串的形式“的RGB ( 255 , 255 , 255 ) ”到“ #ffffff ”
css.rgb2hex = function(rgbString) {
if (typeof (rgbString) != string || !defined(rgbString.match)) { return null; }
var result = rgbString.match(/^\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*/);
if (result == null) { return rgbString; }
var rgb = +result[1] 16 | +result[2] 8 | +result[3];
var hex = ;
var digits = 0123456789abcdef;
while (rgb != 0) {
hex = digits.charAt(rgb 0xf) + hex;
rgb = 4;
}
while (hex.length 6) { hex = '0' + hex; }
return # + hex;
};
// 转换字符样式
css.hyphen2camel = function(property) {
if (!defined(property) |……
阅读全文
2008-05-07 21:28:34
摘要:源码
/* ******************************************************************* */
/* EVENT FUNCTIONS */
/* ******************************************************************* */
var Event = (function() {
var ev = {};
//阻止事件冒泡
ev.stopBubble = function(e) {
// 如果传入了事件对象,那么就是非IE浏览器
if (e)
// 因此它支持W3C的stopPropagation
e.stopPropagation();
else
// 否则,我们得使用IE的方式取消事件冒泡
window.event.cancelBubble = true;
};
//防止发生默认浏览器行为
ev.stopDefault = function(e) {
// 防止默认浏览器行为(W3C)
if (e) e.preventDefault();
// IE中防止浏览器行为的捷径
return false;
};
//
// 由 Dean Edwards 所编写的addEvent/removeEvent 2005
// 由Tino Zijdel整理
// http://dean.edwards.name/weblog/2005/10/add-event/
ev.addEvent = function(element, type, handler) {
//为每个事件处理函数赋予一个独立的ID
if (!handler.$$guid) handler.$$guid = ev.addEvent.guid++;
……
阅读全文