中国进出口商品交易会开发总结
中国进出口商品交易会(The China Import and Export Fair,简称:广交会),创办于1957年4月25日,每年春秋两季在广州举办,由商务部和广东省人民政府联合主办,中国对外贸易中心承办。是中国目前历史最长、层次最高、规模最大、商品种类最全、到会采购商最多且分布国别地区最广、成交效果最好的综合性国际贸易盛会,被誉为“中国第一展”。
广交会贸易方式灵活多样,除传统的看样成交外,还举办网上交易会广交会以出口贸易为主,也做进口生意,还可以开展多种形式的经济技术合作与交流,以及商检、保险、运输、广告、咨询等业务活动。广交会展馆座落于广州琶洲岛,总建筑面积110万平方米,室内展厅总面积33.8万平方米,室外展场面积4.36万平方米。
2020年6月15日至24日,第127届广交会在网上举办,为期10天。
环境问题
环境上应该是最混乱的,因为这环境问题浪费了很多时间精力,人人都感觉这里有问题。我们必须修正解决这些问题,避免无谓的时间精力付出。
常见的环境问题有:
新建了分支而没有公开说明;发布聚合异常;发布后被别人覆盖;发布后被别人还原;分支互相冲突;数据库服务器有时无法访问;提交编译错误的代码;服务器故障无法访问......。
应该多部署几个测试环境
后期开发加密解密数据功能提交测试时,部署是和以前一样的测试环境,导致数据一会被加密,一会没加密,两部分人都受很大影响,浪费了很多时间。所以应该多搞几个测试环境,减少干扰。
项目应该部署能本地运行
现在的项目,只提供从Kestrel运行环境,从Apollo读取配置。一旦那些配置服务器或者其它功能的服务器出故障,就会造成整个组工作都瘫痪的严重情况,这种情况多次发生,严重阻碍了项目推进。应该在每个api解决方案下建立一个新的本地运行的项目,从本地读取配置数据,运行的时候保持本地配置和Apollo配置一致。这样可以有效避免这类问题。
建立本地运行环境,前端可以直接访问
后端开发好接口之后,要发布到开发环境,前端才能调用。但是在开发过程中,开发环境出了状况,比如服务器不能访问,做好的新接口被别人发布覆盖404,新接口不明原因无新数据变化等等。所以因为优先建立本地运行环境,访问前端通过IP直接访问接口。这样可以在本地排除程序,省略多次发布;修正成功,发布到开发服务器,出了问题可以集中精力查找环境问题。
监理本地环境和开发环境,故障类型也不一样,可以比较高效的定位问题,提高开发效率。
Git分支过多,应该在项目中用开关参数代替
项目中经常为了一个需求增加一个分支,结果导致以下几个情况:
1 没有通知到位,有的人开发的分支就错了;
2 分支合并困难,互相冲突,有时要合并一两小时;
3 发布时互相覆盖,导致别人的接口功能调用404或者调用结果还是修正前的等异常;
所以应该谨慎增加新分支,而改用在项目中参加开关参数,这样可以功用一个分支,一致的项目代码,不需要用过多精力合并分支,发布时只要修改开关参数,就可以启用或者禁用某些功能,而不会担心覆盖掉别人另外分支的程序。
消息通知
重要消息在企业微信群说后,应该再次发邮件通知所有人。企业微信群发消息的人很多,消息混杂,再次查找以前重要消息很困难。所以重要消息,比如Git分支重要变动,应该发在每个人的工作邮箱中。
TAPD中的Bug变更,应该少发到个人的工作邮箱。所有Bug变更可以在TAPD站点看到每个人的分配任务,没有必要发到邮箱。而且bug变更太多,工作邮箱收到这些邮件通知很多。这些价值不大的邮件很在个人邮箱中掩盖其它重要邮箱内容。
编程问题
控制器基类继承没有用好
项目中Controller都继承了一个基类BaseController,但是在这个BaseController中空空如也,没有任何代码,或者代码很少,只有一个壳子。面向对象的三个基本特征是:封装、继承、多态,这个大家都知道。
在这里面向对象特性的继承只是用了一个形式,没有真正利用起来。应该把Controller类中一些公用的方法和属性放到ControllerBase中,比如登录人信息EvosClaims,导出Excel方法等等。
IMediator滥用
Controller中接口的实现,大都是用 await _imediatoR.Send(command)。其实接口实现中大都是一对一的方法调用,很少一对多的消息发送;为了这个imediatoR调用,还要为每个使用imediatoR的接口建一个CommandHandler类文件。这样工作量加大,项目结构更复杂。这是没有必要的,大部分情况不需要使用ImediatoR,只是直接使用方法调用就可以了,简便快捷。
Excel导出封装成一个方法
项目中多处用到了Excel导出功能,大家都照着复制粘贴,很多地方代码重复,不知道为什么没有封存一个方法。不过我已经做了,放在BaseController下,大家以后就可以调用await ExportExcel返回FileContentResult结果。
Dapper和EFCore没有返回多个结果功能
项目现有封装的Dapper和EF功能缺失。在某些并发要求高的情况下,我们需要减少数据库连接次数,把几条语句一次性执行完,返回DataSet结果,再从DataSet从取出多个结果。但是项目现有封装的Dapper和EFCore功能缺少这一基本功能。我是自己临时写了一点,解决了这个问题。但是这样导致开发时间加长,以后应该在基础类库中补齐这一功能。
手工拼接复杂SQL容易出错
应该开发SQL语句构建器,辅助手工拼接复杂SQL。手工拼接的SQL容易出错,比如少一个逗号,或者多一个其它符号,字段名称错误,所以现在基本是用流行的ORM简单调用,避免了出错。但是有些情况下,我们不得不手写复杂SQL语句,很多bug也由此产生。我做了一点根据类型或者属性自动生成部分SQL的类,可以供人调用,减少出错,以后还需完善这个功能。
减少使用interface
项目中需要一个数据查询类,就建立interface,而后实现一个class,最后用Autofac进行依赖注入。其实这除了增加了工作量增加,其它没有任何好处。因为这个数据查询类只是在本项目中内部使用,不对外供人使用,技术框架已经确定,不会有数据库选型上的变动,查询数据只用这一个类就可以了,未来也不需要其它实现方式,因此这里不适应面向接口编程,不需要创建interface。
依赖注入适量使用
先看一下相关定义。控制反转是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection, DI)。在一个项目中,一些类只是在这个项目内部使用,本身是和这个项目紧密关联的,没有办法解耦,也没有必要解耦。很多项目用了很多依赖注入,其实开发者的目的是为了学习或者演示依赖注入技术。从项目需要上讲,只需要在关键的地方使用依赖注入。
interface依赖注入具有传染性
项目很多地方只告诉你定义的interface,没有告诉实现这个interface的class。当你需要使用相关功能的时候,不得不在构造函数中把所有要使用的interface都列出来。
public DemoController(IMediator mediator, IExcelExport excelService,
IProm iprom, IRestfulApi restful, ISession session,
ISend isend, IOrderCode iorder)
我的解决方法是定义一个超级interface,把需要的interface作为它属性,在它的实现类中的构造函数传入所有需要参数
public interface ISuper
{
IntefaceA Intefacea { get; }
IntefaceB Intefaceb { get; }
IntefaceC Intefacec { get; }
IntefaceC Intefaced { get; }
}
public class SuperIC: ISuper
{
public IntefaceA Intefacea{ get; private set; }
public IntefaceB Intefaceb { get; private set; }
public IntefaceC Intefacec { get; private set; }
public IntefaceD Intefaced { get; private set; }
public InvitationSuperQuery(
IntefaceA Intefacea
,IntefaceB Intefaceb
, IntefaceC Intefacec
, IntefaceC Intefaced
)
{
this.Intefacea = Intefacea;
this.Intefaceb = Intefaceb;
this.Intefacec = Intefacec;
this.Intefacec = Intefaced;
}
}
使用它的使用这样就可以
public DemoController(ISuper isuper)
全局异常拦截需要改进
在全局异常拦截的HttpGlobalExceptionFilter中,把所有异常全部拦截并且返回客户端了,导致调试时难以触发VS的异常调试功能。应该增加代码,处理调试模式下抛出异常,这样方便调试。
Exception改进
在项目中,只要是Exception就打印它的Message和调用堆栈信息。其中很多是Exception包含InnerException,最重要的错误信息在InnerException中,所以如果当前Exception的InnerException不为null,则打印InnerException的Message和调用堆栈信息。
在这里我提供一个获取顶层InnerException的方法。
public static Exception GetTopInnerException(Exception ex)
{
Exception temp = ex;
while(temp.InnerException!=null)
temp =temp.InnerException;
return temp;
}
api接口问题
api应该改进
有不少接口在路由上定义的只有参数,没有其它常量字符,这导致确认某个接口很麻烦,应该每个接口路由上都尽量带一个重复少的常量字符串,这样有利于从前端的url中快速确认相关接口。
接口统一用POST方式
现在的接口大部分是POST方式,但是还有不少接口是用GET方式。我在和前端对接的过程中,好几次是因为误把GET方式请求用处POST方式,导致调用失败。POST方式是必需的,GET方式是可用可不用的,所以应该规定接口统一只用POST方式,减少失误。
采购商和主办方api应该分开
比如说采购商获取自己下属的采购员列表和主办方获取某一个采购商下属的采购员列表,因为请求和参数和返回的结果是一致的,为了赶时间,用了同一个接口。但是这样不合理,在做鉴权已经其它操作的时候老是搅在一起,比较麻烦。合理的做法是,给主办方和采购商各建一个控制器:SponsorController和PurchaserController,在它们下面都实现GetBuyser方法,不同的权限验证后调用同一个获取数据的方法。这样做虽然多了一点工作量,但是结构清晰,有利于鉴权和其它操作,有利于后来的需求改动而造成的相互影响。
ApiOperateResult异常改进
检测参数错误时用自定义异常带ApiOperateResult属性抛出,在全局异常HttpGlobalExceptionFilter中捕捉这类异常,
取出ApiOperateResult,异常拦截,不返回到客户端
返回的失败json中返回失败码字符
前端访问接口,很多情况下返回失败提示。可以在返回的json结果中加一个code字段,每个地方返回失败信息的失败码字符不一样。这样当前端把返回的结果发给你时,你可以在项目中搜索失败码字符,因为是不重复的,搜索到的地方就是问题所在的地方。这样十分有利于问题定位。
工具类问题
基础平台提供的类库可用方法太少
提供的功能比较少。比如Drapper相关接口,没有返回DataSet;比如自己实现的查询的DataSet,只有找不到把DataTable转化成实体类List的方法;比如导入Excel接口,只有两个方法,很多情况下不能满足需要。
应该开发测试数据导入工具
现在开发环境和测试环境是分开的,测试人员在测试环境测试出的问题,在开发环境和不方便复现,应该开发一个小工具,方便简单设置后一键从测试环境导入数据到开发环境。
应该开发证件P图软件
注册国内采购商时,需要上传身份证、名片、营业执照等图片到腾讯服务上进行OCR识别,OCR识别能力不错,导致上传验证成功的图片只能使用一次,下次测试上传图片时,还需要找新号码、新姓名的图片,大家只好自己P图或者找别人P一下图。我看到老员工测试F**S**P****给图片P图很简单,只是用画图打开图片,用一个长条矩形填充底色,覆盖号码姓名等,然后在矩形上输入特定字体的号码,可见给证件图片P图很简单。应该安排基础平台的人用winform做一个P图片小软件,用GDI+在图片上绘图就可以了,十分简单。开发后提供给大家使用,可以减少很多测试找图片的时间,加快进度,以后的项目也可以继续使用。
聚合服务
简化
聚合服务当然是非常重要的。当前项目的做法是,在实际提供接口的项目中做好Controller和其它方法之后,再复制一份到聚合服务项目,在实际实现的地方修改成访问实际接口的服务器,还有的又在聚合服务里。这样复制的做法和软件复用思想南辕北辙,每一次新加接口,都要复制修改提交等等,多做了很多重复性工作,还增加了出错几率,已经版本合并和发布产生冲突等等问题。而且聚合服务提供的是多个项目服务的聚合,文件量是这些项目文件的总和,非常臃肿。
应该明确聚合服务是不提供实际功能的,它的核心工作是代理转发,应该尽量少附加其它功能到聚合服务上。用我这样的做法可以避免以上的问题。
[Route("Demo/{serviceName}")]
[HttpPost]
public async Task<string> PostInvitationData(string serviceName, [FromBody] string data)
{
string result = await _apiProxy.PostData($"api/demo/${serviceName}", data);
return result;
}
这样在路由进行匹配url的做法,比原先控制器中每一个接口都要重新定义一般要简单方便的多。
Excel导出
还有一个重要问题,Excel导出的功能这样不能实现,这是因为上面只能实现对json的返回,而基础部门提供组件实现的太死,提供的请求中,没有把底层的类开放出来。要解决这个问题,只能让基础平台租的人修改组件,提供从url请求返回HttpResponse,我再从Response.Headers中判断内容,再返回结果给客户端。
总体
项目框架使用了多个流行技术,.Net Core,MediatR,Swagger,Jenkins,EFCore,Drapper,Apollo,Kubernetes ,Consul,Vue;可以说展示了最新最全的流行技术。但是基础的程序质量有不小改进空间,面向接口编程应该用,但是不能到处用;继承用了,但是没有用到它优点。做工作都很忙,好像从来没有开会总结一下。管理比较混乱,人员流动大;开发和测试环境故障多,只靠加班;这又造成加班太多,开发效率低下,低级失误增多;很多人承受加班不了而离开,新来人对项目不熟;如此循环。基础平台部提供组件功能弱,和应用部门需求脱节。