2019城镇单位就业人员平均工资出炉 收入稳步增长

各行各业工资水平怎么样?哪些领域工资高?哪些行业增长快?日前,国家统计局发布2019年城镇非私营单位、城镇私营单位和规模以上企业分岗位就业人员年平均工资情况。数据显示,2019年,全国城镇单位就业人员平均工资稳步增长,公共服务和消费升级相关行业,科教、信息和交通等新兴行业工资均呈现较快增长势头。

6行业年均工资超10万元

首先,工资整体走势怎么样?

3组平均数据来勾勒——2019年全国规模以上企业就业人员年平均工资为75229元,比上年名义增长10%;全国城镇非私营单位就业人员年平均工资为90501元,比上年实际增长6.8%;城镇私营单位就业人员年平均工资为53604元,比上年实际增长5.2%。

“2019年,我国经济运行总体平稳、稳中有进,就业形势保持稳定,全国城镇单位就业人员平均工资稳步增长。”国家统计局人口和就业统计司副司长孟灿文说。

哪些行业工资高?

分行业门类看——信息传输、软件和信息技术服务业工资最高,金融业、科学研究和技术服务业位居前三。

在城镇私营单位中,年平均工资最高的三个行业分别为信息传输、软件和信息技术服务业85301元,金融业76107元,科学研究和技术服务业67642元。城镇非私营单位中,则分别是信息传输、软件和信息技术服务业161352元,科学研究和技术服务业133459元,金融业131405元。

在非私营单位中,共有6个行业年平均收入超过10万元。除了上述三个行业,还包括卫生和社会工作,电力、热力、燃气及水生产供应业,文化、体育和娱乐业。

行业红火带动工资上涨

哪些行业工资增长快?

“随着供给侧结构性改革不断深化,‘三去一降一补’成果进一步巩固,带动相关行业平均工资保持较快增长态势。”孟灿文说。

这在采矿和制造领域表现明显。2019年,采矿业就业人员在城镇非私营单位和城镇私营单位年平均工资分别增长11.8%和12.7%。制造业就业人员在城镇非私营单位和城镇私营单位年平均工资分别增长8.4%和7.3%。

高技术相关制造业和一些短板领域就业人员平均工资也实现较快增长。城镇非私营单位中,医药制造业及铁路、船舶、航空航天和其他运输设备制造业就业人员年平均工资分别增长13.0%和10.3%,土地管理业和水利管理业就业人员年平均工资分别增长23.4%和13.3%。

另一方面,与公共服务和消费升级相关的行业平均工资较快增长。

随着人民生活水平提高,文化事业和文化产业加快发展。2019年城镇非私营单位中,广播电视电影和录音制作业、新闻和出版业就业人员年平均工资分别增长13.7%和13.4%。

居民生活消费相关服务业就业人员平均工资持续增长。2019年城镇非私营单位中,与民生和消费密切相关的机动车电子产品和日用产品修理业、居民服务业、邮政业就业人员年平均工资分别增长16.6%、10.8%和10.4%。

新兴行业工资上涨有潜力

从此次发布的数据来看,科教、信息和交通等新兴行业工资水平呈现可观潜力。

数据显示,2019年全年研究与试验发展经费支出比上年增长10.5%,城镇非私营单位中,研究和试验发展、科技推广和应用服务业、高等教育就业人员年平均工资分别增长15.9%、10.2%和13.2%,分别高于全国平均工资增速6.1、0.4和3.4个百分点。

孟灿文指出,“创新驱动战略深入实施,研发投入不断加大,落实以增加知识价值为导向的分配政策,实行更加灵活的薪酬制度,科研和高等教育领域就业人员平均工资快速增长。”

信息技术广泛渗透,也为一批就业人员带来不错的收入。

一方面,网上零售快速发展。2019年全国实物商品网上零售额比上年增长19.5%,相应地,城镇非私营单位中批发和零售业就业人员年平均工资增长10.5%,高于全国平均工资增速0.7个百分点。

另一方面,互联网应用不断拓展。城镇非私营单位中,信息传输、软件和信息技术服务业中的互联网和相关服务业就业人员年平均工资增长13.6%;城镇私营单位中,信息传输、软件和信息技术服务业就业人员年平均工资增长11.8%,增速均比上年加快。

同时,随着交通基础设施改善,人流物流增加,交通领域就业人员平均工资稳步提升。2019年城镇非私营单位中,航空运输业和铁路运输业就业人员年平均工资分别增长11.5%和10.0%。

统计局:4月份规模以上工业增加值增长3.9%

中新网5月15日电 据国家统计局网站消息,4月份,规模以上工业增加值同比实际增长3.9%(以下增加值增速均为扣除价格因素的实际增长率),上月为下降1.1%。从环比看,4月份,规模以上工业增加值比上月增长2.27%。1—4月份,规模以上工业增加值同比下降4.9%。

分三大门类看,4月份,采矿业增加值同比增长0.3%,增速比上月回落3.9个百分点;制造业增长5.0%,上月为下降1.8%;电力、热力、燃气及水生产和供应业增长0.2%,上月为下降1.6%。

分经济类型看,4月份,国有控股企业增加值同比增长0.5%;股份制企业增长4.0%,外商及港澳台商投资企业增长3.9%;私营企业增长7.0%。

分行业看,4月份,41个大类行业中有28个行业增加值保持同比增长。农副食品加工业增长3.0%,纺织业增长2.0%,化学原料和化学制品制造业增长3.2%,非金属矿物制品业增长4.2%,黑色金属冶炼和压延加工业增长4.6%,有色金属冶炼和压延加工业增长6.9%,通用设备制造业增长7.5%,专用设备制造业增长14.3%,汽车制造业增长5.8%,铁路、船舶、航空航天和其他运输设备制造业增长5.7%,电气机械和器材制造业增长9.0%,计算机、通信和其他电子设备制造业增长11.8%,电力、热力生产和供应业下降0.2%。

分地区看,4月份,东部地区增加值同比增长5.3%,中部地区增长4.5%,西部地区增长5.1%,东北地区增长2.8%。

分产品看,4月份,612种产品中有367种产品同比增长。钢材10701万吨,同比增长3.6%;水泥22347万吨,增长3.8%;十种有色金属493万吨,增长3.8%;乙烯168万吨,下降1.4%;汽车210.0万辆,增长5.1%,其中,新能源汽车7.7万辆,下降17.2%;发电量5543亿千瓦时,增长0.3%;原油加工量5385万吨,增长0.8%。

4月份,工业企业产品销售率为98.0%,比上年同月提高0.8个百分点;工业企业实现出口交货值9790亿元,同比名义增长1.1%。

12亿人用微信一年“扫码”8.58万亿元,约占我国GDP 9%

腾讯微信与中国信通院共同发布《2019-2020微信就业影响力报告》,与微信生态相关的就业情况得到披露。

报告称,据微信“码上经济课题组”测算,2019年,微信生态带来的码上经济规模达到8.58万亿元,约占我国GDP(99.0865万亿元)的9%。

报告显示,2019年微信带动就业岗位2963万个,其中直接带动就业岗位达2601万个,同比增长16%,自2014年以来年均增长22%。间接带动就业岗位362万个。

在微信就业人群中,年轻人、大学本科生为主力。55.8%的生态主体超过一半员工年龄在16-29岁之间,55.0%的生态主体超过一半员工是普通本科学历。生态内女性就业比例达到47.5%。2019年,微信公众平台就业岗位在中西部和东北省份比例达到43.6%,小程序则为29.8%。

另外,2019年微信带动的直接就业机会中有1519万个是兼职就业,占总直接就业机会近6成。目前基于微信的兼职就业主要包括以技能输出为主的教育类服务、投稿写作类、自媒体类、电商类、理财类、表情设计类等。同时,在微信公众号、小程序、支付等运营、开发、 设计和维护中也存在一定数量的兼职就业人群。

IT之家了解到,据腾讯财报,在今年第一季度,微信月活跃账户超过12亿。

CoreDNS解析异常记录

CoreDNS解析异常记录 

异常情况:集群是用kubespray部署的4个worknode,coredns默认部署2个deployment。今天发现部署了coredns的node上的pod正常解析内部域名,而另外2个未运行coredns的node却无法解析。

 配置文件:

 下图中我们看到coredns2个pod分别在node1与node2上,只要分配到这2节点上的deployment都可以正常解析。

其他节点无法解析:

 处理过程:

正常来说所有的pod都是通过coredns来进行集群内域名解析的,我也搞不清楚为啥其他两个node没有跑coredns则就无法解析后面再研究。所以我临时的解决方法是扩容coredns让每个node都跑。

1、修改 ConfigMap 中的 dns-autoscaler(coredns自动扩容保证高可用)

kubectl edit configmap dns-autoscaler --namespace=kube-system

2、修改key:linear

  • coresPerReplica: 按照核心数目来计算副本集(replicas = cores / coresPerReplica)
  • nodesPerReplica:按照节点数目来计算副本集(replicas = nodes / nodesPerReplica)
  • min:最小副本数(默认为2,我先有4个节点改为4)
  • max:最大副本数
  • preventSinglePointFailure:防止单点故障

公式

replicas = max( ceil( cores × 1/coresPerReplica ) , ceil( nodes × 1/nodesPerReplica ) )

 

 

 

[Abp vNext 入坑分享] – 2.简化项目结构

一、简要说明

本篇文章根据我自己的需要对项目结果进行简化,让项目结构更符合我自己的要求,同时让项目跑起来。仅供参考

二、具体步骤

2.1卸载掉对我来说目前使用不上的项目,identityserver,mongodb,httpapi.client,以及对应的test项目

 

 

 

 

2.2删除掉下图红框中的包与类文件,由于我后期会创建一个独立的项目来做migrations,所以不需要在HttpApi.Host里面直接使用EF相关的操作,而且个人认为这样会模糊掉abpvnext的层级,混乱了层级的职责。

 

 

 

上图的报错都是由于我删除了相关的包引起的,因此跳转到相关文件中,把所有的报错行,全部删除。同时由于我没有启用redis的服务,所以要把下图的redis服务也先行注释掉。

2.3将启动模式修改成:如下图,同时把原来输出日志到文件的模式修改成:console(),以便在控制台时能很直观的看到Log。调试项目,则可以启动成功了。

 

 

 

 

 

2.4在src下面增加DbMigrations类库,注意:此处只能选择.netcore类型的类库,不能是只属于netstandard这样会导致无法使用。创建后的样子如下,这样就可以使用此项目进行migration操作了,且不会影响主线代码。

 

 

 

1.关于DbM_LearnDbContext这个类,如果你能保证整个项目的所有开发人员都只能使用codefirst进行开发的话,可以直接继承主的LearnDbContext,这样LearnDbContext的所有DbSet都会得到继承,则不需要再重复写dbset。

2.若是codefirstdbfirst混用的情况,则不要继承

3.单纯dbfirst的话,此项目可以去掉

Unity ML-agents 一、初次尝试

前言

曾在高二寒假的时候,跟表哥在外面玩,当时他问我有没有想过以后要做什么,我愣了一下,回答不上来。是的,从没想过以后要做什么,只是一直在完成学校、老师安排的任务,于是那之后半年,我一直在思考,大学要学什么。在大二下期中之后,我觉得自己还是对游戏更感兴趣,便想到以后想做游戏。于是,高考后填志愿,填的都是计算机专业。在大一的时候,自学了一段时间的 Unity,到大二在实验室接触强化学习之后,就想着用 RL 来做游戏 AI,但后来一直在做数据挖掘相关的内容,基本上以参加比赛为主。直到去年参加上海的谷歌开发者节,了解到 ML-agents 之后,就十分的想尝试。

然后由于疫情,直到现在还在家里咸鱼,前段时间一边咸鱼一边投简历,奈何自己水平太低又偏偏想投算法岗,直到现在也没有几次面试机会 orz。最近就想继续当年未完成的 Unity 的学习,顺便学习 ML-agents,回到原点,重新出发。

初试 ML-agents

环境配置

既然要尝试,肯定免不了环境配置
目前我的环境为:

  1. Win10
  2. Tensorflow 2.0
  3. ML-agents 0.15.0
  4. Unity 2019.3.1f1
    关于 Tensorflow 2.0 的安装,参考我之前的博客,Unity 的安装,推荐先下载 Unity Hub,通过 Unity Hub 可以管理不同版本的 Unity,下载戳这里

ML-agents 的安装有两种方式
一是直接 pip

pip install mlagents

二是从官方 github 中 clone 整个项目,然后 cd 到目录中

pip install -e ./ml-agents-envs
pip install -e ./ml-agents

到这里,环境配置就算完成了

跑个 Demo 先吧

在 Unity Hub 中导入刚刚从 github 上 clone 的项目

选择 Unity 版本之后打开

然后打开 3DBall 这个场景

点击运行的话可以直接看到效果

接着,开始尝试自己训练,打开命令行,进入到之前 clone 下来的项目目录中,并在目录中创建一个名为 summaries 的文件夹

然后输入

mlagents-learn config/trainer_config.yaml --run-id=test --train

在出现图标之后,切换到 Unity 运行项目,就可以看见开始训练了

可以看出来一开的效果是很差的,完全控制不好,这次训练大概训练 40w+ 步,到后面就很稳了

注:项目目录中尽量不要出现中文,下午在尝试的时候一直报错,后来更改目录之后发现成功了,不清楚是不是路径中有中文的原因
报错内容

 "The Unity environment took too long to respond. Make sure that :\n"
mlagents_envs.exception.UnityTimeOutException: The Unity environment took too long to respond. Make sure that :
         The environment does not need user interaction to launch
         The Agents are linked to the appropriate Brains
         The environment and the Python interface have compatible versions.

训练完成后,我们可以在 models 目录下看到刚刚训练好的模型,重命名一下,然后把模型拖到 TFModel 目录中

接着打开 prefabs 中的 3DBall,点击其中的 agent,然后将 Behavior 改成我们刚刚拖到 TFModel 目录中的模型

点击运行,发现控制的十分稳定,跟一开始差不多。至此,初试完毕。

小节

这里我们安装配置了 ML-agnets 的相关环境,并运行了个 Demo 来熟悉了遍流程,后面将开始尝试自己搭建环境,训练 AI,也不知道能不能捣鼓出来,23333。

kafka消息分区机制原理

背景

kafka如何支撑海量消息的集中写入?

答案就是消息分区。

核心思想是:负载均衡,采用合适的分区策略把消息写到不同的broker上的分区中;

其它的产品中有类似的思想。

比如monogodb, es 里面叫做 shard;   hbase叫region,  cassdra叫vnode;

消息的三层结构

如下图:

即  topic -> partition -> message ;

topic是逻辑上的消息容器;

partition实际承载消息,分布在不同的kafka的broke上;

message即具体的消息。

分区策略

round-robin轮询

消息按照分区挨个的写。

randomness随机分区

随机的找一个分区写入,代码如下:

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
return ThreadLocalRandom.current().nextInt(partitions.size());

key

相同的key的消息写到固定的分区中

自定义分区

必须完成两步:

1,自定义分区实现类,需要实现org.apache.kafka.clients.producer.Partitioner接口。

主要是实现下面的方法:

int partition(String topic, Object key, byte[] keyBytes, 
              Object value, byte[] valueBytes, Cluster cluster);

比如按照区域分区。

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
return partitions.stream().filter(p -> isSouth(p.leader().host()))
    .map(PartitionInfo::partition).findAny().get();

2,显示配置生产者端的参数partitioner.class为具体的类

系统默认:如果消息有key,按照key分区策略,否则按照轮询策略。

小结

kafka的分区实现消息的高吞吐量的主要依托,主要是实现了写的负载均衡。可以指定各种负载均衡算法。
负载均衡算法非常重要,需要极力避免消息分区不均的情况,可能给消费者带来性能瓶颈。

小结如下:

原创不易,点赞关注支持一下吧!转载请注明出处,让我们互通有无,共同进步,欢迎沟通交流。
我会持续分享Java软件编程知识和程序员发展职业之路,欢迎关注,我整理了这些年编程学习的各种资源,关注公众号‘李福春持续输出’,发送’学习资料’分享给你!

必杀技:当报错信息看不出原因时,怎么办?

今天遇到了一个错误,一般的错误提示会很明显,一看就知道是什么问题。今天遇到的这个说实话真的不好找原因,一般在这种情况下该怎么解决呢?

分享下我的思路吧,不一定是最好的,至少有用。

直接上图吧,下面是报错信息:

为了方便查看,我把最重要的信息提取出来,如下:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [com/cxytiandi/kitty/web/config/WebAppConfigurer.class]: Invocation of init method failed; nested exception is java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy

java.lang.ArrayStoreException这个确实平时很少遇到,看了下源码,这个是数组存储异常。比如下图中我框起来的部分就清楚的表示了在什么场景下会出现这个异常。

也就是在存储的时候类型不一致,然后就报错了呗!

第二个需要关注的错误信息是WebAppConfigurer.class,这个还算挺明确的,告诉我哪个类有问题,然后我看了下对应的代码,也就手动的映射了资源路径而已。

于是我就想,是不是这里面哪个类加载的时候出问题了,我把WebAppConfigurer直接去掉了,但是并没什么用,后面还是报的相同的错误,只不过是提示另一个类了,就是WebMvcAutoConfiguration。

[org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy

所以说这些错误信息没能直接定位问题就是这个原因,我们要关注的还是java.lang.ArrayStoreException这个异常,只要找到这个异常发生的地方就能解决了。

下面只能借助于IDEA强大的调试功能了,增加一个Java Exception Breakpoints了。

然后debug模式重启,果不其然就报错的时候就进断点了。

这下终于找到原因了,parseClassValue的时候出问题了,Class就是 org.springframework.cloud.sleuth.instrument.web.client.feign.TraceFeignClientAutoConfiguration。

这个类是我当时在Sleuth中扩展Sentinel对Feign支持的时候做了一些修改,没想到居然出了Bug。

下面给大家说明下真正的原因吧,在这个扩展模块中sentinel的依赖是可选的,如下:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel</artifactId>
    <optional>true</optional>
</dependency>

刚好报错的项目中不需要用到Sentinel,但是用到了Sleuth和Feign,所以TraceFeignClientAutoConfiguration生效了。主要还是Conditional都满足条件了。

项目中又没显示指定依赖Sentinel,这个类自然加载失败。

所以解决办法就是要么加Sentinel依赖,要么就是在@ConditionalOnClass中加上Sentinel的类,这样只有当在Sentinel的类在classpath中存在的时候才会加载,如果项目没依赖Sentinel那么就不加载,这样就没问题了。

最后总结下吧,主要还是要找到真正问题发生在什么地方,有的时候异常信息给出的并不一定是真正的地方,只是有关联而已。

当你封装的模块设置了optional=true的时候,在对应的配置类加载生效也需要用@ConditionalOnClass来进行判断启用,否则就会出现上面的问题。

模板参数的“右值引用”是转发引用

在C++11中,&&不再只有逻辑与的含义,还可能是右值引用:

void f(int&& i);

但也不尽然,&&还可能是转发引用:

template<typename T>
void g(T&& obj);

“转发引用”(forwarding reference)旧称“通用引用”(universal reference),它的“通用”之处在于你可以拿一个左值绑定给转发引用,但不能给右值引用:

void f(int&& i) { }

template<typename T>
void g(T&& obj) { }

int main()
{
    int n = 2;
    f(1);
//  f(n); // error
    g(1);
    g(n);
}

一个函数的参数要想成为转发引用,必须满足:

  • 参数类型为T&&,没有constvolatile

  • T必须是该函数的模板参数。

换言之,以下函数的参数都不是转发引用:

template<typename T>
void f(const T&&);
template<typename T>
void g(typename std::remove_reference<T>&&);
template<typename T>
class A
{
    template<typename U>
    void h(T&&, const U&);
};

另一种情况是auto&&变量也可以成为转发引用:

auto&& vec = foo();

所以写范围for循环的最好方法是用auto&&

std::vector<int> vec;
for (auto&& i : vec)
{
    // ...
}

有一个例外,当auto&&右边是初始化列表,如auto&& l = {1, 2, 3};时,该变量为std::initializer_list<int>&&类型。

转发引用,是用来转发的。只有当你的意图是转发参数时,才写转发引用T&&,否则最好把const T&T&&写成重载(如果需要的话还可以写T&,还有不常用的const T&&;其中T是具体类型而非模板参数)。

转发一个转发引用需要用std::forward,定义在<utility>中:

#include <utility>

template<typename... Args>
void f(Args&&... args) { }

template<typename T>
void g(T&& obj)
{
    f(std::forward<T>(obj));
}

template<typename... Args>
void h(Args&&... args)
{
    f(std::forward<Args>(args)...);
}

调用g有几种可能的参数:

  • int i = 1; g(i);Tint&,调用g(int&)

  • const int j = 2; g(j);Tconst int&,调用g(const int&)

  • int k = 3; g(std::move(k));g(4);Tint(不是int&&哦!),调用g(int&&)

你也许会疑惑,为什么std::move不需要<T>std::forward需要呢?这得从std::forward的签名说起:

template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&) noexcept;
template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&&) noexcept;

调用std::forward时,编译器无法根据std::remove_reference_t<T>反推出T,从而实例化函数模板,因此<T>需要手动指明。

但是这并没有从根本上回答问题,或者可以进一步引出新的问题——为什么std::forward的参数不定义成T&&呢?

原因很简单,T&&会把T&const T&T&&const T&&(以及对应的volatile)都吃掉,有了T&&以后,再写T&也没用。

且慢,T&&参数在传入函数是会匹配到T&&吗?

#include <iostream>
#include <utility>

void foo(int&)
{
    std::cout << "int&" << std::endl;
}

void foo(const int&)
{
    std::cout << "const int&" << std::endl;
}

void foo(int&&)
{
    std::cout << "int&&" << std::endl;
}

void bar(int&& i)
{
    foo(i);
}

int main()
{
    int i;
    bar(std::move(i));
}

不会!程序输出int&。在函数bar中,i是一个左值,其类型为int的右值引用。更直接一点,它有名字,所以它是左值。

因此,如果std::forward没有手动指定的模板参数,它将不能区分T&T&&——那将是“糟糕转发”,而不是“完美转发”了。

最后分析一下std::forward的实现,以下代码来自libstdc++:

template<typename _Tp>
  constexpr _Tp&&
  forward(typename std::remove_reference<_Tp>::type& __t) noexcept
  { return static_cast<_Tp&&>(__t); }

template<typename _Tp>
  constexpr _Tp&&
  forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
  {
    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
                  " substituting _Tp is an lvalue reference type");
    return static_cast<_Tp&&>(__t);
  }
  • 当转发引用T&& obj绑定左值int&时,匹配第一个重载,_TpTint&,返回类型_Tp&&int&(引用折叠:& && &&&& &都折叠为&,只有&& &&折叠为&&);

  • const int&同理;

  • 当转发引用绑定右值int&&时,匹配第二个重载,_Tpint,返回类型为int&&

  • const int&&同理。

综上,std::forward能完美转发。

程序员总是要在Stack Overflow上撞撞墙才能学会一点东西。

IdentityServer4 QuckStart 授权与自定义Claims

最近在折腾IdentityServer4,为了简单,直接使用了官方给的QuickStart示例项目作为基础进行搭建。有一说一,为了保护一个API,感觉花费的时间比写一个API还要多。

本文基于ASP.NET CORE 3.1, IdentityServer4 3.1.3。代码皆为关键代码,贴全了太多了。

好不容易跑起来了,最终的任务要落实到授权的工作上来。在API中使用Authorize用来限制用户的访问。

[Route("api/[controller]")]
[Authorize(Roles = "Administrator")]
[ApiController]
public class UserInfoController : ControllerBase
{
    /// <summary>
    /// 无参GET请求
    /// </summary>
    /// <returns></returns>
    [HttpGet()]
    [ProducesResponseType(typeof(ReturnData<IEnumerable<UserInfo>>), Status200OK)]
    public async Task<ActionResult> Get()
    {
        var info = new Info<UserInfo>();
        return Ok(new ReturnData<IEnumerable<UserInfo>>(await info.Get()));
    }

然而在使用的时候,虽然正确取得授权,但是却无法正常访问API,一直提示401没有授权错误。仔细检查,发现IdentityServer4返回的内容并没有返回role的JwtClaimTypes,没有它,Authorize无法正常工作。

{
    "nbf": 1587301921,
    "exp": 1587305521,
    "iss": "http://localhost:5000",
    "aud": "MonitoringSystemApi",
    "client_id": "webClient",
    "sub": "c6c18d4d-c28e-4de5-86dd-779121216204",
    "auth_time": 1587301921,
    "idp": "local",
    "scope": [
        "roles",
        "MonitoringSystemApi",
        "offline_access"
    ],
    "amr": [
        "pwd"
    ]
}

实现

查看Config.cs,IdentityServer4默认只返回两种IdentityResource:openid和profile。按照官方的说法,这个东西定义的内容会返回到用户的token。参考。那么就果断给它安排。

public static IEnumerable<IdentityResource> Ids =>
new List<IdentityResource>
{
    new IdentityResources.OpenId(),
    new IdentityResources.Profile(),
    new IdentityResource ("roles", new List<string> { JwtClaimTypes.Role }){ Required = true}
};

public static IEnumerable<Client> Clients =>
    new List<Client>
    {
        new Client
        {
            ClientId = "webClient",
            ClientSecrets = { new Secret("secret".Sha256()) },
            AllowOfflineAccess = true,
            AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
            // scopes that client has access to
            AllowedScopes = {
                "roles",

                "MonitoringSystemApi" }
        },

执行之前,需要确保数据库中的用户数据,已经包含role的Claim。

//添加用户代码
bob = new ApplicationUser
{
    UserName = "bob"
};
var result = userMgr.CreateAsync(bob, "Pass123$").Result;
if (!result.Succeeded)
{
    throw new Exception(result.Errors.First().Description);
}
result = userMgr.AddClaimsAsync(bob, new Claim[]{
new Claim(JwtClaimTypes.Role, "Administrator"),
new Claim(JwtClaimTypes.Name, "Bob Smith"),

运行程序,返回值依旧没有任何变化,很挫败,只能继续折腾。
研究通过实现IProfileService达到自定义Cliams。文章写的很详细,我这就不重复了,我实际试验过,可以成功。

但是文章末尾的注意,很重要。

“那么, 通过profileservice颁发的claims, 任意clients都能拿到”

说明这个优先级是非常高的,可以覆盖所有的行为,当然我们可以在IProfileService的实现上对权限进行进一步的设置,不过还是挺麻烦的。参考实现参考官方

作为懒人,必然不想再费劲去折腾权限的问题,那么是否有简单点的办法呢?

网上有一些问答说到了可以通过设置Scopes来达到目的。不过过于久远,IdentityServer4已经没有这个独立的类了,说是已经被ApiResource取代了。

直觉上这个东西应该是指示要保护的API的相关内容的,好像和这个没啥关系,不过也只能死马当活马医了。修改config.cs,最终如下内容:

public static IEnumerable<ApiResource> Apis =>
new List<ApiResource>
{
    new ApiResource("pls", new[]{ "role"}),
};

public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
    ClientId = "webClient",
    ClientSecrets = { new Secret("secret".Sha256()) },
    AllowOfflineAccess = true,
    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    // scopes that client has access to
    AllowedScopes = {
        "pls"
        }
},

返回结果如下:

{
    "nbf": 1587301799,
    "exp": 1587305399,
    "iss": "http://localhost:5000",
    "aud": "pls",
    "client_id": "webClient",
    "sub": "c6c18d4d-c28e-4de5-86dd-779121216204",
    "auth_time": 1587301799,
    "idp": "local",
    "role": "Administrator",
    "scope": [
        "pls",
        "offline_access"
    ],
    "amr": [
        "pwd"
    ]
}

终于看见心心念念的自定义Claim(role),可以去访问API了。

注意,在Client中也有个Claims,添加了role并且设置AlwaysSendClientClaimsAlwaysIncludeUserClaimsInIdToken之后,会在token中添加client_roie字段,这个是没办法用与授权的,可以理解为IdentityServer4直接指定了Client角色,并不是Identity中的角色概念。

后记

回过头来仔细看官方的文档,ApiResource中的UserClaims就是用来干这个的,折腾了半天,不如当时仔细看看文档了。

  • 友情链接