开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Gravitee使用指南(一)网关部署-官方镜像部署

发表于 2021-07-14

首先,我们需要准备部署网关的物理环境,我搭建测试环境使用的是:

1.8核16G的Centos服务器两台

2.三节点的Mongodb集群一个

3.2核4G Redis实例一个

4.一套完整的ELK环境,主要是elaticsearch

本文章是用过的官方编译好放在DockerHub的镜像进项部署,所以我们需要先准备好Docker环境,Docker的安装我这里不再赘述,可以直接看官方文档。

根据官方文档描述,我们在启动网关时,需要配置网关的限流中间件数据库,可以使用redis和mongodb两种数据来进行限制,和进行统计报表的elaticsearch

注意这有个坑,最新的版本已经不再支持redis作为reate limit来进行使用

1626249726752.jpg

所以我们在准备好数据库环境之后,开始部署网关应用,我们来看一下官方文档的部署命令

1
2
3
4
5
6
bash复制代码docker run  \
--publish 82:8082 \
--name gateway \
--env GRAVITEE_MANAGEMENT_MONGODB_URI=mongodb://username:password@mongohost:27017/dbname
--detach \
graviteeio/apim-gateway:latest

官方实例中式通过环境变量来指定了mongodb的信息,然后告诉你,如果想进行更多详细的配置,需要自己编写yml文件来进行更详细的配置,我这里贴一下完整的全量配置yml文件,量非常的大,大家慢慢看。

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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
yml复制代码############################################################################################################
#################################### Gravitee.IO Gateway - Configuration ###################################
############################################################################################################

############################################################################################################
# This file is the general configuration of Gravitee.IO Gateway:
# - Properties (and respective default values) in comment are provided for information.
# - You can reference other property by using ${property.name} syntax
# - gravitee.home property is automatically set-up by launcher and refers to the installation path. Do not override it !
#
# Please have a look to http://docs.gravitee.io/ for more options and fine-grained granularity
############################################################################################################

# Gateway HTTP server
#http:
# port: 8082
# host: 0.0.0.0
# idleTimeout: 0
# tcpKeepAlive: true
# compressionSupported: false
# maxHeaderSize: 8192
# maxChunkSize: 8192
# maxInitialLineLength: 4096
# instances: 0
# requestTimeout: 0
# secured: false
# alpn: false
# ssl:
# sni: false
# clientAuth: none # Supports none, request, requires
# tlsProtocols: TLSv1.2, TLSv1.3
# tlsCiphers: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
# keystore:
# type: jks # Supports jks, pem, pkcs12
# path: ${gravitee.home}/security/keystore.jks
# password: secret
# truststore:
# type: jks # Supports jks, pem, pkcs12
# path: ${gravitee.home}/security/truststore.jks
# password: secret
# websocket:
# enabled: false
# subProtocols: v10.stomp, v11.stomp, v12.stomp
# perMessageWebSocketCompressionSupported: true
# perFrameWebSocketCompressionSupported: true

# Plugins repository
#plugins:
# path:
# - ${gravitee.home}/plugins
# - ${gravitee.home}/my-custom-plugins

# If a plugin is already installed (but with a different version), management node does not start anymore
# failOnDuplicate: true

# Management repository is used to store global configuration such as APIs, applications, apikeys, ...
# This is the default configuration using MongoDB (single server)
# For more information about MongoDB configuration, please have a look to:
# - http://api.mongodb.org/java/current/com/mongodb/MongoClientOptions.html
management:
type: mongodb
mongodb:
dbname: ${ds.mongodb.dbname}
host: ${ds.mongodb.host}
port: ${ds.mongodb.port}
# username:
# password:
# connectionsPerHost: 0
# connectTimeout: 500
# maxWaitTime: 120000
# socketTimeout: 500
# socketKeepAlive: false
# maxConnectionLifeTime: 0
# maxConnectionIdleTime: 0
# serverSelectionTimeout: 0
# description: gravitee.io
# heartbeatFrequency: 10000
# minHeartbeatFrequency: 500
# heartbeatConnectTimeout: 1000
# heartbeatSocketTimeout: 20000
# localThreshold: 15
# minConnectionsPerHost: 0
# sslEnabled: false
# keystore: # path to KeyStore (when sslEnabled is true)
# keystorePassword: # KeyStore password
# keyPassword: # password for recovering keys in the KeyStore
# threadsAllowedToBlockForConnectionMultiplier: 5
# cursorFinalizerEnabled: true
# possible values are 1,2,3... (the number of node) or 'majority'
# writeConcern: 1
# wtimeout: 0
# journal: true

# Management repository: single MongoDB using URI
# For more information about MongoDB configuration using URI, please have a look to:
# - http://api.mongodb.org/java/current/com/mongodb/MongoClientURI.html
#management:
# type: mongodb
# mongodb:
# uri: mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

# Management repository: clustered MongoDB
#management:
# type: mongodb
# mongodb:
# servers:
# - host: mongo1
# port: 27017
# - host: mongo2
# port: 27017
# dbname: ${ds.mongodb.dbname}
# connectTimeout: 500
# socketTimeout: 250

# When defining rate-limiting policy, the gateway has to store data to share with other gateway instances.
# In this example, we are using MongoDB to store counters.
ratelimit:
type: mongodb
mongodb:
uri: mongodb://${ds.mongodb.host}:${ds.mongodb.port}/${ds.mongodb.dbname}

cache:
type: ehcache

# Reporters configuration (used to store reporting monitoring data, request metrics, healthchecks and others...
# All reporters are enabled by default. To stop one of them, you have to add the property 'enabled: false'
reporters:
# logging configuration
# logging:
# max_size: -1 # max size per API log content respectively : client-request, client-response, proxy-request and proxy-response in MB (-1 means no limit)
# excluded_response_types: video.*|audio.*|image.*|application\/octet-stream|application\/pdf # Response content types to exclude in logging (must be a regular expression)
# Elasticsearch reporter
elasticsearch:
enabled: true # Is the reporter enabled or not (default to true)
endpoints:
- http://${ds.elastic.host}:${ds.elastic.port}
# lifecycle:
# policy_property_name: index.lifecycle.name #for openDistro, use 'opendistro.index_state_management.policy_id' instead of 'index.lifecycle.name'
# policies:
# monitor: my_policy ## ILM policy for the gravitee-monitor-* indexes
# request: my_policy ## ILM policy for the gravitee-request-* indexes
# health: my_policy ## ILM policy for the gravitee-health-* indexes
# log: my_policy ## ILM policy for the gravitee-log-* indexes
# index: gravitee
# index_per_type: true
# bulk:
# actions: 1000 # Number of requests action before flush
# flush_interval: 5 # Flush interval in seconds
# settings:
# number_of_shards: 1
# number_of_replicas: 1
# refresh_interval: 5s
# pipeline:
# plugins:
# ingest: geoip, user_agent # geoip and user_agent are enabled by default for elasticsearch version above 7.x
# security:
# username: user
# password: secret
# http:
# timeout: 30000 # in milliseconds
# proxy:
# type: HTTP #HTTP, SOCK4, SOCK5
# http:
# host: localhost
# port: 3128
# username: user
# password: secret
# https:
# host: localhost
# port: 3128
# username: user
# password: secret
# template_mapping:
# path: ${gravitee.home}/config/reporter/elasticsearch/templates
# extended_request_mapping: request.ftl
file:
enabled: false # Is the reporter enabled or not (default to false)
# fileName: ${gravitee.home}/metrics/%s-yyyy_mm_dd
# output: json # Can be csv, json, elasticsearch or message_pack

# Gateway service configurations. Provided values are default values.
# All services are enabled by default. To stop one of them, you have to add the property 'enabled: false' (See the
# 'local' service for an example).
services:
core:
http:
enabled: true
port: 18082
host: localhost
authentication:
# authentication type to be used for the core services
# - none : to disable authentication
# - basic : to use basic authentication
# default is "basic"
type: basic
users:
admin: adminadmin

# The thresholds to determine if a probe is healthy or not
# health:
# threshold:
# cpu: # Default is 80%
# memory: # Default is 80%

# Synchronization daemon used to keep the gateway state in sync with the configuration from the management repository
# Be aware that, by disabling it, the gateway will not be sync with the configuration done through management API
# and management UI
sync:
# Synchronization is done each 5 seconds
cron: '*/5 * * * * *'
distributed: false # By enabling this mode, data synchronization process is distributed over clustered API gateways.

# Service used to store and cache api-keys from the management repository to avoid direct repository communication
# while serving requests.
apikeyscache:
delay: 10000
unit: MILLISECONDS
threads: 3 # Threads core size used to retrieve api-keys from repository.

# Service used to store and cache subscriptions from the management repository to avoid direct repository communication
# while serving requests.
subscriptions:
delay: 10000
unit: MILLISECONDS
threads: 3 # Threads core size used to retrieve subscriptions from repository.

# Local registry service.
# This registry is used to load API Definition with json format from the file system. By doing so, you do not need
# to configure your API using the web console or the rest API (but you need to know and understand the json descriptor
# format to make it work....)
local:
enabled: false
path: ${gravitee.home}/apis # The path to API descriptors

# Gateway monitoring service.
# This service retrieves metrics like os / process / jvm metrics and send them to an underlying reporting service.
monitoring:
delay: 5000
unit: MILLISECONDS
distributed: false # By enabling this mode, data monitoring gathering process is distributed over clustered API gateways.

# metrics service
metrics:
enabled: false
# default: local, http_method, http_code
# labels:
# - local
# - remote
# - http_method
# - http_code
# - http_path
prometheus:
enabled: true

# heartbeat
# heartbeat:
# enabled: true
# delay: 5000
# unit: MILLISECONDS
# storeSystemProperties: true

#handlers:
# request:
# # manage traceparent header defined by W3C trace-context specification
# trace-context:
# enabled: false
# # possible values: hex, uuid. Default: uuid.
# format: uuid
# transaction:
# # Default: X-Gravitee-Transaction-Id.
# header: X-Gravitee-Transaction-Id
# headers:
# # Override X-Forwarded-Prefix with context path. Disabled by default.
# x-forwarded-prefix: false
# request:
# # Default: X-Gravitee-Request-Id.
# header: X-Gravitee-Request-Id

# Referenced properties
ds:
mongodb:
dbname: gravitee
host: localhost
port: 27017
elastic:
host: localhost
port: 9200

#system:
# # Proxy configuration that can be used to proxy request to api endpoints (see endpoint http configuration -> Use system proxy).
# proxy:
# type: HTTP #HTTP, SOCK4, SOCK5
# host: localhost
# port: 3128
# username: user
# password: secret

# Organizations and Environments configuration
# Associate this gateway to a list of environments belonging to organizations. This is a list of environment hrids.
#organizations: mycompany
#environments: dev,qa

# Sharding tags configuration
# Allows to define inclusion/exclusion sharding tags to only deploy a part of APIs. To exclude just prefix the tag with '!'.
#tags: products,stocks,!international
#zone: national-products

# Multi-tenant configuration
# Allow only a single-value
#tenant: europe

#policy:
# Customize the api-key header and / or query parameter.
# Set an empty value to prohibit its use.
# api-key:
# header: X-Gravitee-Api-Key
# param: api-key

#el:
# Allows to define which methods or classes are accessible to the Expression Language engine (/!\ caution, changing default whitelist may expose you to security issues).
# A complete list of default whitelist methods can be found here (https://raw.githubusercontent.com/gravitee-io/gravitee-expression-language/master/src/main/resources/whitelist).
# whitelist:
# Allows to define if the specified list of method or classes should be append to the default one or should replace it.
# We recommend you to always choose 'append' unless you absolutely know what you are doing.
# mode: append
# Define the list of classes or methods to append (or set) to made accessible to the Expression Language.
# start with 'method' to allow a specific method (complete signature).
# start with 'class' to allow a complete class. All methods of the class will then be accessible.
# list:
# Ex: allow access to DateTimeFormatter.ofLocalizedDate(FormatStyle) method
# - method java.time.format.DateTimeFormatter ofLocalizedDate java.time.format.FormatStyle
# Ex: allow access to all methods of DateTimeFormatter class
# - class java.time.format.DateTimeFormatter

#groovy:
# Allows to define which methods, fields, constructors, annotations or classes are accessible to the Groovy Script (/!\ caution, changing default whitelist may expose you to security issues).
# A complete list of default whitelist methods can be found here (https://raw.githubusercontent.com/gravitee-io/gravitee-policy-groovy/master/src/main/resources/groovy-whitelist).
# whitelist:
# Allows to define if the specified list of methods, fields, constructors or classes should be append to the default one or should replace it.
# We recommend you to always choose 'append' unless you absolutely know what you are doing.
# mode: append
# Define the list of classes, methods, constructors, fields or annotations to append (or set) to made accessible to the Groovy Script.
# start with 'method' to allow a specific method (complete signature).
# start with 'class' to allow a complete class. All methods, constructors and fields of the class will then be accessible.
# start with 'new' to allow a specific constructor (complete signature).
# start with 'field' to allow access to a specific field of a class.
# start with 'annotation' to allow use of a specific annotation.
# list:
# Ex: allow access to DateTimeFormatter.ofLocalizedDate(FormatStyle) method
# - method java.time.format.DateTimeFormatter ofLocalizedDate java.time.format.FormatStyle
# Ex: allow access to all methods, constructors and fields of DateTimeFormatter class
# - class java.time.format.DateTimeFormatter
# Ex: allow usage of field Integer.MAX_VALUE
# - field java.lang.Integer MAX_VALUE
# Ex: allow usage of @Override annotation
# - annotation java.lang.Override

# If you want to create cluster of nodes, you can change the Hazelcast file to configure the Hz network
# Clustering capabilities can be used for:
# - Distributed sync process
# - Distributed rate-limiting / quota counters
#cluster:
# hazelcast:
# config:
# path: ${gravitee.home}/config/hazelcast.xml

问题来了,文档中告诉了你配置文件怎么写,但是愣是没告诉你怎么让容器正确的读取到这个配置文件,我一句MMP就爆出了口,然后我去翻了一下源代码,找到了答案,网关会默认读取:/etc/gravitee-gateway/gravitee.yml这个位置的配置文件,所以我们在启动容器时需要给他挂载一个写好的配置文件到容器的这个目录上去,当然你也可以直接在build的时候就把配置就打到镜像的这个位置上去,我选择的是挂载,方便修改,所以最后的部署命令变为了:

1
docker复制代码docker run -p 8082:8082 --name openapi-gateway -v /home/openapi/gateway:/etc/gravitee-gateway -d graviteeio/apim-gateway:latest

这里给贴一个最基础的核心配置文件出来,只要写上这几个核心配置,就可以启动网关,别问我为什么知道,说出来全是泪。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yml复制代码management:
type: mongodb
mongodb:
uri: mongodb://你的mongodb数据库
ratelimit: #这个老版本的可以选redis MongoDB jdbc 新版本不在支持redis,我都是全量用mongodb
type: mongodb
mongodb:
uri: mongodb://你的mongodb数据库
cache:
type: ehcache #这玩意是必须配置的,不配置会报错
reporters:
elasticsearch:
enabled: true
endpoints:
- http://172.17.0.1:9200

正常启动网关之后,我们看一下网关的日志,我们会看到网关读取了我们挂载的配置文件,加载了所有默认插件并且开始监听8082端口开始处理请求,如果启动失败查看一下日志信息,大多数都是因为配置不正确或者数据库连接不上等问题。

从输出日志中我们还可以看出来几个坑就是jvm参数和一些系统的时区之类的配置,默认最大的jvm内存只有256,需要我们去自行更改,但是官方文档中并没有标注通过哪些地方进行修改,所以下一篇文章我们会采用源码编译然后自己编译镜像的方式去进行部署,来修改一些关键的自定义配置。

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

rest_framework框架入门

发表于 2021-07-14

rest_framework框架简介
官方网站:www.django-rest-framework.org/

安装

1
复制代码pip install djangorestframework

settings.py文件配置

1
2
3
ini复制代码INSTALLED_APPS = [
'rest_framework'
]

在app中创建一个serializers.py文件
1.jpg

serializers.py内容编写

1
2
3
4
5
6
7
kotlin复制代码from rest_framework import serializers
from .models import Student

class StuSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = '__all__'

views.py文件内容

1
2
3
4
5
6
python复制代码from rest_framework import viewsets
from .serializer import StuSerializer

class StusViewSet(viewsets.ModelViewSet):
queryset = Student.objects.filter(zy_id="4")
serializer_class = StuSerializer

urls.py 文件内容:

1
2
3
4
5
6
7
8
9
10
11
javascript复制代码from django.conf.urls import url
from django.urls import include
from . import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('stus', views.StusViewSet)

urlpatterns = [
url('', include(router.urls)),
]

运行调试
2.jpg

3.jpg

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

啃完这两本书Spring框架在你面前便没有秘密 《Sprin

发表于 2021-07-14

Spring对Java程序员的重要性相信懂的都懂,夸张点甚至可以说是Spring成就了Java。所以一直以来经常会有很多粉丝后台问我怎么学习Spring,这里我只推荐两本书:《Spring实战》4.0和《Spring揭秘》

image

image

相信看到这两本书很多同学会说:就这?能整点新鲜的吗?

是的,这两本书很多博主都已经推荐过了,相信很多同学也看过,但是他们没说为什么要看这两本书,所以,我们今天来看看凭什么要啃这两本书。

前者告诉你怎么用Spring

后者给你简单展示如何用的同时,还告诉你Spring是怎么实现的

两者一起,让你知其然并知其所以然,绝配。

《Spring实战》4.0

首先说一下为什么是4.0而不是3.0或者5.0呢?

这是因为4.0版本的内容相对来说是最全面的,相较于3.0增加了很多内容,而5.0则又把IOC、AOP、redis、nosql、websocket等基础讲解和不常用模块,特别是Redis,还是蛮可惜的。

那为什么要推荐《Spring实战》这本书呢?理由有二

老鸟菜鸟皆宜

1、全书在Spring4.0的基础上有新内容的升级。翻译水平很高,几乎没有读起来不流畅的感觉;

2、作者思路极为清晰,行文张弛有度,重点突出,完全没有读的很累的感觉;

3、请放弃某私人教育机构出版的《XX系列讲义》,直接看这本,境界都不一样。

这可以说是近些年我读过关于Spring的最好的一本书,翻译水平上乘,经典之余完全没有枯燥的感觉,无论老鸟做参考还是小白入门都会受益匪浅。

不可多得的优秀译本

相比于其他普遍存在的翻译恶劣的计算机书籍,本书的翻译出类拔萃,阅读的过程也变得尤为通常。

尽管 Spring 可以说是 Java 后台用得最多的框架之一,Spring 官网的文档则做得差强人意。(无奈,Spring 的模块实在过于繁多)

本书系统地介绍了 Spring 各个基本组件的基本使用,从一开始的 Spring Core 到 Spring AOP 到 Spring MVC,再到 Spring Data、Spring Security 以及最新的 Spring Boot。如此看来,本书可以说是学习 Spring 框架的经典教材,值得推荐。

这里贴一下《Spring实战4.0》的电子版目录给没看过的菜鸟感受一下,这本书的PDF也可以免费分享给大家,点击Spring实战即可获取!

image

image

《Spring揭秘》

关于这本书很多人会说已经太老了,看点新书吧,事实上的确有点旧了,甚至不再版了,但是讲源码的书,贵不在新,而在于条理。最重要的是,目前市面上讲源码的书还没有能跟这本书比肩的。

毕竟我们现在在用的最新版本的Spring、乃至Spring Boot都是从那时的Spring迭代而来的。

**PS:**学习不要局限于手头的资源,有时候书本的价值不在于告诉你什么,而在于让你产生什么疑问,

看到有不懂的,不要怪作者语文功底不行,把疑问记下来,谷歌之,这才是学习的乐趣。

这本书把 Spring 各个模块的背景、设计原理都交代了,可以串起你的 Spring 知识脉络,看完之后心里有点数了。

目前已经绝版,如果需要电子档点击Spring揭秘即可免费获取。

image

image

小结

好了,本次推书就到这里了,还想要什么方面的学习书籍推荐欢迎评论区留言,如果人多的话我会考虑出一个推书专栏,每天给大家推荐一些不错的技术书籍。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java-使用内插字符串 Zircon

发表于 2021-07-14

Zircon

Zircon可以让你在Java语言中使用内插字符串语法

什么叫内插字符串?

字符串插值功能构建在复合格式设置功能的基础之上,提供更具有可读性、更方便的语法,用于将表达式结果包括到结果字符串。

对某个表达式执行计算后,其结果立即转换为一个字符串并包含到结果字符串中

如何使用内插字符串

若要将字符串标识为内插字符串,可在该字符串前面加上 $或f 符号。 可嵌入任何会在内插字符串中返回值的有效 JAVA 表达式。

特性

  1. 支持android、java等所有使用javac的项目
  2. 几乎不会增加额外编译时间
  3. 代码内容支持idea补全提示(需安装idea插件)
  4. 更多内插字符串语法糖支持
  5. 旧有项目引入插件后不需要改动任何代码

效果图

example


使用示例

String text=f" this is F-$String.class.getSimpleName() ";

assert Objects.equals($"Zircon: [ ${text.trim()} ]","Zircon: [ "+text.trim()+" ]");


高级语法糖

  1. 根据字符串前的前缀不同,拥有不同的字符串内插逻辑,请注意区别使用

1. $前缀字符串($-string):

直接使用加号进行字符串的拼接,拥有最快的运行

1. JDK1.6的情况,javac遇到加号(`+`)链接的字符串会自动转化成`StringBuilder.append()`,不存在加号连接字符串额外的对象开销
2. 如果首个子部分为非硬编码字符串,会自动包裹`String.valueOf()`以防止拼接异常#### 2. `f`前缀字符串(`f-string`):

使用String.format函数进行拼接,速度相比$-string较慢

1. 如果是使用大括号包裹的`${}`代码块,你可以指定的每个内插代码块的显示格式:
在代码块的前部,增加一个以`%`号开始的`String.format`格式化标识,并且使用`:`与后续代码块进行分隔 。形如:`${%03d:12}`会输出`012`的格式化结果
  1. 未被大括号{}包裹的简易代码块,拥有java特性自动识别代码范围的功能
1. 请不要完全依赖该功能,只能简单判断括号匹配及后续引用,复杂语句请使用`${}`
2. 如果要使用该功能,请注意根据插件提示的java代码注入范围来判断是否使用错误
  1. 为减少转义双引号的情况,${}内可以使用单引号以替代双引号
1. 如果需要使用单引号以声明`char`类型,你需要使用`\'C\'`进行转义

插件引入

使用Gradle构建项目

Step 1. 在你的根目录项目build.gradle文件中进行如下操作

1
2
3
4
5
6
7
markdown复制代码    allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}

}

Step 2. 在需要使用插件的module的build.gradle文件中进行如下操作

1
2
3
4
arduino复制代码    dependencies {
...
annotationProcessor 'com.github.122006.Zircon:javac:版本号'
}

当前版本号:2.3

如果编译安卓项目,module的build.gradle文件中加入以下代码,以配置javac参数

1
2
3
javascript复制代码	tasks.withType(JavaCompile) {
options.compilerArgs << "-Xplugin:ZrString"
}

如果编译标准java项目(非安卓项目),以配置javac参数

1
2
3
arduino复制代码    compileJava {
options.compilerArgs << "-Xplugin:ZrString"
}

使用Maven构建项目

Step 1. 增加依赖

1
2
3
4
5
xml复制代码    <dependency>
<groupId>com.github.122006.Zircon</groupId>
<artifactId>javac</artifactId>
<version>版本号</version>
</dependency>

Step 2. 配置jitpack仓库

1
2
3
4
5
6
xml复制代码    <repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>

当前版本号:2.3

Step 2. 配置javac参数 ("-Xplugin:ZrString")

1
2
3
4
5
6
7
8
9
xml复制代码    <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-Xplugin:ZrString</arg>
</compilerArgs>
</configuration>
</plugin>

安装IDEA插件

本插件尚未发布至idea仓库,请手动下载安装

  1. 点击 这里[ijplugin.zip] 进行下载
  2. 下载文件后拖动至idea中自动安装

其他注意事项

  1. 请注意保持idea插件更新到最新

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

docker 安装部署MySQL 57

发表于 2021-07-14

docker 安装部署MySQL 5.7

拉取MySQL5.7镜像

1
2
3
4
5
docker复制代码docker pull mysql:5.7   # 拉取 mysql 5.7
docker pull mysql # 拉取最新版mysql镜像

#查看镜像是否拉取成功
docker images

构建MySQL数据库容器

1
2
3
4
5
6
MySQL复制代码docker run -d -e \
MYSQL_ROOT_PASSWORD=123456 --name mysql-5.7 \
-v /bjfc/docker/mysql/data:/var/lib/mysql \
-v /etc/localtime:/etc/localtime \
-p 3306:3306 \
mysql:5.7
  • –name:容器名,此处命名为mysql-5.7
  • -e:配置信息,此处配置mysql的root用户的登陆密码
  • -p:端口映射,此处映射 主机3306端口 到 容器的3306端口
  • -d:后台运行容器,保证在退出终端后容器继续运行
  • -v:主机和容器的目录映射关系,”:”前为主机目录,之后为容器目录
  • 注意:这里需要关注/etc/localtime这个文件目录的映射,docker容器中的时间使用的是“标准时间”,与“北京时间”相差8小时,这里解决办法有很多,这只是其中的一种解决办法,能保证存储时间的正确性*

查看MySQL容器,登录MySQL

1
2
3
4
5
MySQL容器复制代码docker ps   #查看所有容器

docker exec -it mysql-5.7 /bin/bash #进入docker容器中

mysql -uroot -pfc888888 #登录MySQL

使用远程连接软件时要注意一个问题

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
远程链接修改复制代码
#登录mysql
mysql -u root -p
ALTER USER 'root'@'localhost' IDENTIFIED BY '123456 ';

#添加远程登录用户
CREATE USER 'hctm'@'%' IDENTIFIED WITH mysql_native_password BY '123456 ';
GRANT ALL PRIVILEGES ON *.* TO 'hctm'@'%';

#或者用下面的方法开通远程访问权限
#grant all privileges on *.* to root@'%' identified by "123456";
# mysql使用mysql数据库中的user表来管理权限,修改user表就可以修改权限(只有root账号可以修改)

mysql> use mysql;
Database changed

mysql> select host,user from user;
+--------------+------+-------------------------------------------+
| host | user | password |
+--------------+------+-------------------------------------------+
| localhost | root | *A731AEBFB621E354CD41BAF207D884A609E81F5E |
| 192.168.1.1 | root | *A731AEBFB621E354CD41BAF207D884A609E81F5E |
+--------------+------+-------------------------------------------+
2 rows in set (0.00 sec)

mysql> grant all privileges on *.* to root@'%' identified by "password";
Query OK, 0 rows affected (0.00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

mysql> select host,user,password from user;
+--------------+------+-------------------------------------------+
| host | user | password |
+--------------+------+-------------------------------------------+
| localhost | root | *A731AEBFB621E354CD41BAF207D884A609E81F5E |
| 192.168.1.1 | root | *A731AEBFB621E354CD41BAF207D884A609E81F5E |
| % | root | *A731AEBFB621E354CD41BAF207D884A609E81F5E |
+--------------+------+-------------------------------------------+
3 rows in set (0.00 sec)

在容器内,MySQL表名大小写问题

此操作对于有强迫症的码农非常有用,能解决你内心的痛苦
解决此问题只需要知道两点

  • 修改mysqld.conf 配置参数:lower_case_table_names
    不同系统,该参数的默认值是不同的。
    • lower_case_table_names = 1 表名存储在磁盘是小写的,但是比较的时候是不区分大小写
    • lower_case_table_names=0 表名存储为给定的大小和比较是区分大小写的
    • lower_case_table_names=2, 表名存储为给定的大小写但是比较的时候是小写的
    注:windows环境默认 0:linux环境默认 2:macos环境默认
  • MySQL容器内部mysqld.conf配置文件在:/etc/mysql/mysql.conf.d
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
mysqld.conf复制代码docker exec -it mysql-5.7 /bin/bash

mysql -u root -p #登录MySQL

mysql> show global variables like '%lower_case%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| lower_case_file_system | OFF |
| lower_case_table_names | 0 |
+------------------------+-------+
2 rows in set (0.01 sec)

#lower_case_file_system表示当前系统文件是否大小写敏感,只读参数,无法修改。
#ON表示大小写不敏感。
#OFF表示大小写敏感。
#由于在容器中未安装vi或vim,所以将配置文件从容器复制出来,修改之后再复制到容器内。
#将文件从容器中复制到宿主机/data目录:

#通过exit 命令退出MySQL数据,在通过exit命令退出容器,进入的data文件夹

cd /data

docker cp mysql-5.7:/etc/mysql/mysql.conf.d/mysqld.cnf /data #将mysqld.cnf文件复制到data文件夹

vim mysqld.cnf #修改文件

[mysqld]
lower_case_table_names=1
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
log-error = /var/log/mysql/error.log
# By default we only accept connections from localhost
"mysqld.cnf" 39L, 1635C

#修改完成后再将 mysqld.cnf 文件cp到docker容器中
docker cp /data/mysqld.cnf mysql-5.7:/etc/mysql/mysql.conf.d

docker restart mysql-5.7 # 容器MySQL容器

#登录MySQL数据,查看修改后结果
mysql> show global variables like '%lower_case%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| lower_case_file_system | OFF |
| lower_case_table_names | 1 |
+------------------------+-------+
2 rows in set (0.01 sec)

此操作可以解决MySQL表名大小写问题,尤其对于activiti自动建立的表有奇效,会将表明统一小写仅此而已(解决强迫症)。
注意:
此操作在已存在表的数据库进行操作时,需要注意将已经大写的表进行备份,并且删除,在执行上面操作后,在将保存的表数据进行恢复即可。如无备份,你在上进行以上操作的时候,会将你所有大写的表进行隐藏,只是隐藏,修改lower_case_table_names =0时会自动恢复。但是,如果没有删除表,就进行了此操作,那么会在数据库出现一张大写的表和同名的小写表。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【py小游戏系列】井字棋,儿时的回忆|Python 主题月

发表于 2021-07-14

本文正在参加「Python主题月」,详情查看 活动链接

hello大家好,今天我又发现了个有趣的小玩意。我是专写有趣小玩意的老诗。

相信大家对于井字棋都并不陌生。现在也能找到各种各样的井字棋小游戏玩。那么你们自己是否会编写呢?接下来老诗用python教大家编写井字棋小游戏。

老规矩,先上效果图

111111.gif

井字棋,英文名叫Tic-Tac-Toe,是一种在3 * 3格子上进行的连珠游戏,和五子棋类似,由于棋盘一般不画边框,格线排成井字故得名。游戏需要的工具仅为纸和笔,然后由分别代表O和X的两个游戏者轮流在格子里留下标记(一般来说先手者为X),任意三个标记形成一条直线,则为获胜。

先画格子

1
2
3
4
5
6
scss复制代码def grid():
"Draw tic-tac-toe grid."
line(-67, 200, -67, -200)
line(67, 200, 67, -200)
line(-200, -67, 200, -67)
line(-200, 67, 200, 67)

先是画出四条线。范围是-200~200

画x函数

1
2
3
4
scss复制代码def drawx(x, y):
"Draw X player."
line(x, y, x + 133, y + 133)
line(x, y + 133, x + 133, y)

这个x其实就是两条线,传入x,y只要计算好位置就可以画出来。

画圆函数

1
2
3
4
5
6
scss复制代码def drawo(x, y):
"Draw O player."
up()
goto(x + 67, y + 5)
down()
circle(62)

o就是画圆,传入x,y后以x + 67, y + 5为圆心,62为半径画出圆。

随意点击,计算位置

1
2
3
4
5
6
ini复制代码def floor(value):
"Round value down to grid with square size 133."
return ((value + 200) // 133) * 133 - 200

state = {'player': 0}
players = [drawx, drawo]

image.png

我们要知道,我们用鼠标人工点击的时候,鼠标的位置都是并非标准的。然后我们不制定一个标准画图位置,那么我们点击之后画图的位置就会变歪了。所以我们需要通过我们点击的位置,再计算一个标准的传入位置。

检查点击动作

1
2
3
4
5
6
7
8
9
ini复制代码def tap(x, y):
"Draw X or O in tapped square."
x = floor(x)
y = floor(y)
player = state['player']
draw = players[player]
draw(x, y)
update()
state['player'] = not player

这是在检查点击动作,然后响应,调用画图。

整体而言,游戏并不是太难,也很容易就可以学会。上面的思想还是说的比较清楚的。有需要拿完整源码的话,请移步到公众号:诗一样的代码。既然进来了,原创不易。小伙伴点个赞再走呗。

合集系列:

【py小游戏系列】吃豆人,儿时的回忆|Python 主题月

【py小游戏系列】贪吃蛇,儿时的回忆|Python 主题月

【py小游戏系列】井字棋,儿时的回忆|Python 主题月

【py小游戏系列】记忆宫殿,儿时的回忆|Python 主题月

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【技术实践】基于Cglib动态代理,实现Spring的AOP

发表于 2021-07-14

作者:小傅哥

博客:bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!😄

一、前言

为什么,你的代码总是糊到猪圈上?

🎙怎么办,知道你在互联网,不知道你在哪个大厂。知道你在加班,不知道你在和哪个产品争辩。知道你在偷懒,不知道你要摸鱼到几点。知道你在搬砖,不知道你在盖哪个猪圈。


当你特别辛苦夜以继日的完成着,每天、每周、每月重复性的工作时,你能获得的成长是最小,得到的回报也是少的。留着最多的汗、拿着最少的钱

可能你一激动开始看源码,但不知道看完的源码能用到什么地方。看设计模式,看的时候懂,但改自己的代码又下不去手。其实一方面是本身技术栈的知识面不足,另外一方面是自己储备的代码也不够。最终也就导致根本没法把一些列的知识串联起来,就像你看了 HashMap,但也联想不到分库分表组件中的数据散列也会用到了 HashMap 中的扰动函数思想和泊松分布验证、看了Spring 源码,也读不出来 Mybatis 是如何解决只定义 Dao 接口就能使用配置或者注解对数据库进行 CRUD 操作、看来 JDK 的动态代理,也想不到 AOP 是如何设计的。所以成体系学习,加强技术栈知识的完整性,才能更好的用上这些学习到的编码能力。

二、目标

到本章节我们将要从 IOC 的实现,转入到关于 AOP(Aspect Oriented Programming) 内容的开发。在软件行业,AOP 意为:面向切面编程,通过预编译的方式和运行期间动态代理实现程序功能功能的统一维护。其实 AOP 也是 OOP 的延续,在 Spring 框架中是一个非常重要的内容,使用 AOP 可以对业务逻辑的各个部分进行隔离,从而使各模块间的业务逻辑耦合度降低,提高代码的可复用性,同时也能提高开发效率。

关于 AOP 的核心技术实现主要是动态代理的使用,就像你可以给一个接口的实现类,使用代理的方式替换掉这个实现类,使用代理类来处理你需要的逻辑。比如:

1
2
3
4
5
6
java复制代码@Test
public void test_proxy_class() {
IUserService userService = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserService.class}, (proxy, method, args) -> "你被代理了!");
String result = userService.queryUserInfo();
System.out.println("测试结果:" + result);
}

代理类的实现基本都大家都见过,那么有了一个基本的思路后,接下来就需要考虑下怎么给方法做代理呢,而不是代理类。另外怎么去代理所有符合某些规则的所有类中方法呢。如果可以代理掉所有类的方法,就可以做一个方法拦截器,给所有被代理的方法添加上一些自定义处理,比如打印日志、记录耗时、监控异常等。

三、方案

在把 AOP 整个切面设计融合到 Spring 前,我们需要解决两个问题,包括:如何给符合规则的方法做代理,以及做完代理方法的案例后,把类的职责拆分出来。而这两个功能点的实现,都是以切面的思想进行设计和开发。如果不是很清楚 AOP 是啥,你可以把切面理解为用刀切韭菜,一根一根切总是有点慢,那么用手(代理)把韭菜捏成一把,用菜刀或者斧头这样不同的拦截操作来处理。而程序中其实也是一样,只不过韭菜变成了方法,菜刀变成了拦截方法。整体设计结构如下图:

  • 就像你在使用 Spring 的 AOP 一样,只处理一些需要被拦截的方法。在拦截方法后,执行你对方法的扩展操作。
  • 那么我们就需要先来实现一个可以代理方法的 Proxy,其实代理方法主要是使用到方法拦截器类处理方法的调用 MethodInterceptor#invoke,而不是直接使用 invoke 方法中的入参 Method method 进行 method.invoke(targetObj, args) 这块是整个使用时的差异。
  • 除了以上的核心功能实现,还需要使用到 org.aspectj.weaver.tools.PointcutParser 处理拦截表达式 "execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))",有了方法代理和处理拦截,我们就可以完成设计出一个 AOP 的雏形了。

四、实现

1. 工程结构

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
java复制代码small-spring-step-11
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── aop
│ │ ├── aspectj
│ │ │ └── AspectJExpressionPointcut.java
│ │ ├── framework
│ │ │ ├── AopProxy.java
│ │ │ ├── Cglib2AopProxy.java
│ │ │ ├── JdkDynamicAopProxy.java
│ │ │ └── ReflectiveMethodInvocation.java
│ │ ├── AdvisedSupport.java
│ │ ├── ClassFilter.java
│ │ ├── MethodMatcher.java
│ │ ├── Pointcut.java
│ │ └── TargetSource.java
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── FactoryBeanRegistrySupport.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── FactoryBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── event
│ │ │ ├── AbstractApplicationEventMulticaster.java
│ │ │ ├── ApplicationContextEvent.java
│ │ │ ├── ApplicationEventMulticaster.java
│ │ │ ├── ContextClosedEvent.java
│ │ │ ├── ContextRefreshedEvent.java
│ │ │ └── SimpleApplicationEventMulticaster.java
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ ├── ApplicationEvent.java
│ │ ├── ApplicationEventPublisher.java
│ │ ├── ApplicationListener.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── IUserService.java
│ ├── UserService.java
│ └── UserServiceInterceptor.java
└── ApiTest.java

工程源码:公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

AOP 切点表达式和使用以及基于 JDK 和 CGLIB 的动态代理类关系,如图 12-2

图 12-2

  • 整个类关系图就是 AOP 实现核心逻辑的地方,上面部分是关于方法的匹配实现,下面从 AopProxy 开始是关于方法的代理操作。
  • AspectJExpressionPointcut 的核心功能主要依赖于 aspectj 组件并处理 Pointcut、ClassFilter,、MethodMatcher 接口实现,专门用于处理类和方法的匹配过滤操作。
  • AopProxy 是代理的抽象对象,它的实现主要是基于 JDK 的代理和 Cglib 代理。在前面章节关于对象的实例化 CglibSubclassingInstantiationStrategy,我们也使用过 Cglib 提供的功能。

2. 代理方法案例

在实现 AOP 的核心功能之前,我们先做一个代理方法的案例,通过这样一个可以概括代理方法的核心全貌,可以让大家更好的理解后续拆解各个方法,设计成解耦功能的 AOP 实现过程。

单元测试

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
java复制代码@Test
public void test_proxy_method() {
// 目标对象(可以替换成任何的目标对象)
Object targetObj = new UserService();
// AOP 代理
IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), targetObj.getClass().getInterfaces(), new InvocationHandler() {
// 方法匹配器
MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))");
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (methodMatcher.matches(method, targetObj.getClass())) {
// 方法拦截器
MethodInterceptor methodInterceptor = invocation -> {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
System.out.println("监控 - Begin By AOP");
System.out.println("方法名称:" + invocation.getMethod().getName());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("监控 - End\r\n");
}
};
// 反射调用
return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));
}
return method.invoke(targetObj, args);
}
});
String result = proxy.queryUserInfo();
System.out.println("测试结果:" + result);
}
  • 首先整个案例的目标是给一个 UserService 当成目标对象,对类中的所有方法进行拦截添加监控信息打印处理。
  • 从案例中你可以看到有代理的实现 Proxy.newProxyInstance,有方法的匹配 MethodMatcher,有反射的调用 invoke(Object proxy, Method method, Object[] args),也用用户自己拦截方法后的操作。这样一看其实和我们使用的 AOP 就非常类似了,只不过你在使用 AOP 的时候是框架已经提供更好的功能,这里是把所有的核心过程给你展示出来了。

测试结果

1
2
3
4
5
6
7
8
java复制代码监控 - Begin By AOP
方法名称:queryUserInfo
方法耗时:86ms
监控 - End

测试结果:小傅哥,100001,深圳

Process finished with exit code 0
  • 从测试结果可以看到我们已经对 UserService#queryUserInfo 方法进行了拦截监控操作,其实后面我们实现的 AOP 就是现在体现出的结果,只不过我们需要把这部分测试的案例解耦为更具有扩展性的各个模块实现。

拆解案例

图 12-3

  • 拆解过程可以参考截图 12-3,我们需要把代理对象拆解出来,因为它可以是 JDK 的实现也可以是 Cglib 的处理。
  • 方法匹配器操作其实已经是一个单独的实现类了,不过我们还需要把传入的目标对象、方法匹配、拦截方法,都进行统一的包装,方便外部调用时进行一个入参透传。
  • 最后其实是 ReflectiveMethodInvocation 的使用,它目前已经是实现 MethodInvocation 接口的一个包装后的类,参数信息包括:调用的对象、调用的方法、调用的入参。

3. 切点表达式

定义接口

cn.bugstack.springframework.aop.Pointcut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public interface Pointcut {

/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never <code>null</code>)
*/
ClassFilter getClassFilter();

/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never <code>null</code>)
*/
MethodMatcher getMethodMatcher();

}
  • 切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类,这两个接口获取都是切点表达式提供的内容。

cn.bugstack.springframework.aop.ClassFilter

1
2
3
4
5
6
7
8
9
10
java复制代码public interface ClassFilter {

/**
* Should the pointcut apply to the given interface or target class?
* @param clazz the candidate target class
* @return whether the advice should apply to the given target class
*/
boolean matches(Class<?> clazz);

}
  • 定义类匹配类,用于切点找到给定的接口和目标类。

cn.bugstack.springframework.aop.MethodMatcher

1
2
3
4
5
6
7
8
9
java复制代码public interface MethodMatcher {

/**
* Perform static checking whether the given method matches. If this
* @return whether or not this method matches statically
*/
boolean matches(Method method, Class<?> targetClass);

}
  • 方法匹配,找到表达式范围内匹配下的目标类和方法。在上文的案例中有所体现:methodMatcher.matches(method, targetObj.getClass())

实现切点表达式类

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
java复制代码public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {

private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();

static {
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
}

private final PointcutExpression pointcutExpression;

public AspectJExpressionPointcut(String expression) {
PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
pointcutExpression = pointcutParser.parsePointcutExpression(expression);
}

@Override
public boolean matches(Class<?> clazz) {
return pointcutExpression.couldMatchJoinPointsInType(clazz);
}

@Override
public boolean matches(Method method, Class<?> targetClass) {
return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
}

@Override
public ClassFilter getClassFilter() {
return this;
}

@Override
public MethodMatcher getMethodMatcher() {
return this;
}

}
  • 切点表达式实现了 Pointcut、ClassFilter、MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用。
  • 匹配 matches:pointcutExpression.couldMatchJoinPointsInType(clazz)、pointcutExpression.matchesMethodExecution(method).alwaysMatches(),这部分内容可以单独测试验证。

匹配验证

1
2
3
4
5
6
7
8
9
10
11
java复制代码@Test
public void test_aop() throws NoSuchMethodException {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.UserService.*(..))");
Class<UserService> clazz = UserService.class;
Method method = clazz.getDeclaredMethod("queryUserInfo");

System.out.println(pointcut.matches(clazz));
System.out.println(pointcut.matches(method, clazz));

// true、true
}
  • 这里单独提供出来一个匹配方法的验证测试,可以看看你拦截的方法与对应的对象是否匹配。

4. 包装切面通知信息

cn.bugstack.springframework.aop.AdvisedSupport

1
2
3
4
5
6
7
8
9
10
11
java复制代码public class AdvisedSupport {

// 被代理的目标对象
private TargetSource targetSource;
// 方法拦截器
private MethodInterceptor methodInterceptor;
// 方法匹配器(检查目标方法是否符合通知条件)
private MethodMatcher methodMatcher;

// ...get/set
}
  • AdvisedSupport,主要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。这和你的业务开发中包装入参是一个道理
  • TargetSource,是一个目标对象,在目标对象类中提供 Object 入参属性,以及获取目标类 TargetClass 信息。
  • MethodInterceptor,是一个具体拦截方法实现类,由用户自己实现 MethodInterceptor#invoke 方法,做具体的处理。像我们本文的案例中是做方法监控处理
  • MethodMatcher,是一个匹配方法的操作,这个对象由 AspectJExpressionPointcut 提供服务。

5. 代理抽象实现(JDK&Cglib)

定义接口

cn.bugstack.springframework.aop.framework

1
2
3
4
5
java复制代码public interface AopProxy {

Object getProxy();

}
  • 定义一个标准接口,用于获取代理类。因为具体实现代理的方式可以有 JDK 方式,也可以是 Cglib 方式,所以定义接口会更加方便管理实现类。

cn.bugstack.springframework.aop.framework.JdkDynamicAopProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

private final AdvisedSupport advised;

public JdkDynamicAopProxy(AdvisedSupport advised) {
this.advised = advised;
}

@Override
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), advised.getTargetSource().getTargetClass(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args));
}
return method.invoke(advised.getTargetSource().getTarget(), args);
}

}
  • 基于 JDK 实现的代理类,需要实现接口 AopProxy、InvocationHandler,这样就可以把代理对象 getProxy 和反射调用方法 invoke 分开处理了。
  • getProxy 方法中的是代理一个对象的操作,需要提供入参 ClassLoader、AdvisedSupport、和当前这个类 this,因为这个类提供了 invoke 方法。
  • invoke 方法中主要处理匹配的方法后,使用用户自己提供的方法拦截实现,做反射调用 methodInterceptor.invoke 。
  • 这里还有一个 ReflectiveMethodInvocation,其他它就是一个入参的包装信息,提供了入参对象:目标对象、方法、入参。

cn.bugstack.springframework.aop.framework.Cglib2AopProxy

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
java复制代码public class Cglib2AopProxy implements AopProxy {

private final AdvisedSupport advised;

public Cglib2AopProxy(AdvisedSupport advised) {
this.advised = advised;
}

@Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
return enhancer.create();
}

private static class DynamicAdvisedInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy);
if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
return advised.getMethodInterceptor().invoke(methodInvocation);
}
return methodInvocation.proceed();
}
}

private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

@Override
public Object proceed() throws Throwable {
return this.methodProxy.invoke(this.target, this.arguments);
}

}

}
  • 基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术处理对象的代理对象生成,因此被代理类不需要实现任何接口。
  • 关于扩展进去的用户拦截方法,主要是在 Enhancer#setCallback 中处理,用户自己的新增的拦截处理。这里可以看到 DynamicAdvisedInterceptor#intercept 匹配方法后做了相应的反射操作。

五、测试

1. 事先准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码public class UserService implements IUserService {

public String queryUserInfo() {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "小傅哥,100001,深圳";
}

public String register(String userName) {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "注册用户:" + userName + " success!";
}

}
  • 在 UserService 中提供了2个不同方法,另外你还可以增加新的类来加入测试。后面我们的测试过程,会给这个两个方法添加我们的拦截处理,打印方法执行耗时。

2. 自定义拦截方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public class UserServiceInterceptor implements MethodInterceptor {

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
System.out.println("监控 - Begin By AOP");
System.out.println("方法名称:" + invocation.getMethod());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("监控 - End\r\n");
}
}

}
  • 用户自定义的拦截方法需要实现 MethodInterceptor 接口的 invoke 方法,使用方式与 Spring AOP 非常相似,也是包装 invocation.proceed() 放行,并在 finally 中添加监控信息。

3. 单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码@Test
public void test_dynamic() {
// 目标对象
IUserService userService = new UserService();

// 组装代理信息
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTargetSource(new TargetSource(userService));
advisedSupport.setMethodInterceptor(new UserServiceInterceptor());
advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"));

// 代理对象(JdkDynamicAopProxy)
IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
// 测试调用
System.out.println("测试结果:" + proxy_jdk.queryUserInfo());

// 代理对象(Cglib2AopProxy)
IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
// 测试调用
System.out.println("测试结果:" + proxy_cglib.register("花花"));
}
  • 整个案例测试了 AOP 在于 Spring 结合前的核心代码,包括什么是目标对象、怎么组装代理信息、如何调用代理对象。
  • AdvisedSupport,包装了目标对象、用户自己实现的拦截方法以及方法匹配表达式。
  • 之后就是分别调用 JdkDynamicAopProxy、Cglib2AopProxy,两个不同方式实现的代理类,看看是否可以成功拦截方法

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码监控 - Begin By AOP
方法名称:public abstract java.lang.String cn.bugstack.springframework.test.bean.IUserService.queryUserInfo()
方法耗时:86ms
监控 - End

测试结果:小傅哥,100001,深圳
监控 - Begin By AOP
方法名称:public java.lang.String cn.bugstack.springframework.test.bean.UserService.register(java.lang.String)
方法耗时:97ms
监控 - End

测试结果:注册用户:花花 success!

Process finished with exit code 0
  • 如 AOP 功能定义一样,我们可以通过这样的代理方式、方法匹配和拦截后,在对应的目标方法下,做了拦截操作进行监控信息打印。

六、总结

  • 从本文对 Proxy#newProxyInstance、MethodInterceptor#invoke,的使用验证切面核心原理以及再把功能拆解到 Spring 框架实现中,可以看到一个貌似复杂的技术其实核心内容往往没有太多,但因为需要为了满足后续更多的扩展就需要进行职责解耦和包装,通过这样设计模式的使用,以此让调用方能更加简化,自身也可以不断按需扩展。
  • AOP 的功能实现目前还没有与 Spring 结合,只是对切面技术的一个具体实现,你可以先学习到如何处理代理对象、过滤方法、拦截方法,以及使用 Cglib 和 JDK 代理的区别,其实这与的技术不只是在 Spring 框架中有所体现,在其他各类需要减少人工硬编码的场景下,都会用到。比如RPC、Mybatis、MQ、分布式任务
  • 一些核心技术的使用上,都是具有很强的关联性的,它们也不是孤立存在的。而这个能把整个技术栈串联起来的过程,需要你来大量的学习、积累、由点到面的铺设,才能在一个知识点的学习拓展到一个知识面和知识体系的建设。

七、系列推荐

  • 《Java 面经手册》PDF,全书 417 页 11.5 万字
  • 工作两三年了,整不明白架构图都画啥?
  • 码农云服务使用学习,部环境、开端口、配域名、弄SSL、搭博客!
  • 毕业前写了20万行代码,让我从成为同学眼里的面霸!
  • Thread.start() ,它是怎么让线程启动的呢?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

号称下一代可视化监控系统,结合SpringBoot使用,贼爽

发表于 2021-07-14

SpringBoot实战电商项目mall(50k+star)地址:github.com/macrozheng/…

摘要

当面对一个复杂的系统时,我们往往需要监控工具来帮助我们解决一些性能问题。比如之前我们使用SpringBoot Admin来监控应用,从而获取到SpringBoot Actuator暴露的指标信息。今天给大家介绍一个功能强大的监控工具Grafana,只要需要用到监控的地方,用它做可视化就对了!

Grafana简介

Grafana是一款开源的数据可视化和分析工具,不管你的指标信息存储在哪里,你都可以用它来可视化这些数据。同时它还具有告警功能,当指标超出指定范围时会提醒你。

Prometheus简介

Prometheus是一款时序数据库,可以简单理解为带时间的MySQL数据库。由于Grafana只能将数据转换成可视化图表,并没有存储功能,所以我们需要结合Prometheus这类时序数据库一起使用。

安装

使用Docker安装Grafana和Prometheus无疑是最简单的,我们接下来将采用此种方式。

  • 首先下载Grafana的Docker镜像;
1
bash复制代码docker pull grafana/grafana
  • 下载完成后运行Grafana;
1
2
bash复制代码docker run -p 3000:3000 --name grafana \
-d grafana/grafana
  • 接下来下载Prometheus的Docker镜像;
1
bash复制代码docker pull prom/prometheus
  • 在/mydata/prometheus/目录下创建Prometheus的配置文件prometheus.yml:
1
2
yaml复制代码global:
scrape_interval: 5s
  • 运行Prometheus,把宿主机中的配置文件prometheus.yml挂载到容器中去;
1
2
3
bash复制代码docker run -p 9090:9090 --name prometheus \
-v /mydata/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
-d prom/prometheus
  • 至此安装完成,是不是很简单!可以通过如下地址访问Grafana,登录账号密码为admin:admin,访问地址:http://192.168.5.78:3000/

  • 登录Grafana后显示界面如下;

  • 其实Prometheus也是有可视化界面的,就是有点简陋,访问地址:http://192.168.5.78:9090/

使用

Grafana已经安装完后,是时候来波实践了,接下来我们来介绍下使用Grafana来监控Linux系统和SpringBoot应用。

监控系统信息

使用node_explorer可以暴露Linux系统的指标信息,然后Prometheus就可以通过定时扫描的方式获取并存储指标信息了。

  • 下载node_explorer的安装包,下载地址:prometheus.io/download/#n…

  • 这次我们直接把node_explorer安装到Linux服务器上(如果使用Docker容器安装,监控的会是Docker容器的指标信息),将下载的安装包解压到指定目录,并修改文件夹名称:
1
2
3
bash复制代码cd /mydata
tar -zxvf node_exporter-1.1.2.linux-amd64.tar.gz
mv node_exporter-1.1.2.linux-amd64 node_exporter
  • 进入解压目录,使用如下命令运行node_explorer,服务将运行在9100端口上;
1
2
bash复制代码cd node_exporter
./node_exporter >log.file 2>&1 &
  • 使用curl命令访问获取指标信息接口,获取到信息表示运行成功;
1
bash复制代码curl http://localhost:9100/metrics
1
2
3
4
5
6
7
8
bash复制代码# HELP promhttp_metric_handler_requests_in_flight Current number of scrapes being served.
# TYPE promhttp_metric_handler_requests_in_flight gauge
promhttp_metric_handler_requests_in_flight 1
# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.
# TYPE promhttp_metric_handler_requests_total counter
promhttp_metric_handler_requests_total{code="200"} 2175
promhttp_metric_handler_requests_total{code="500"} 0
promhttp_metric_handler_requests_total{code="503"} 0
  • 接下来修改Prometheus的配置文件prometheus.yml,创建一个任务定时扫描node_explorer暴露的指标信息;
1
2
3
4
yaml复制代码scrape_configs:
- job_name: node
static_configs:
- targets: ['192.168.5.78:9100']
  • 重启Prometheus容器,可以通过加号->Dashboard来创建仪表盘;

  • 当然你还可以选择去Grafana的仪表盘市场下载一个Dashboard,市场地址:grafana.com/grafana/das…

  • 这里选择了Node Exporter Full这个仪表盘,记住它的ID,访问地址:grafana.com/grafana/das…

  • 选择导入Dashboard并输入ID,最后点击Load即可;

  • 选择数据源为Prometheus,最后点击Import;

  • 导入成功后就可以在Grafana中看到实时监控信息了,是不是够炫酷!

监控SpringBoot应用

监控SpringBoot应用需要依靠actuator及micrometer,通过暴露actuator的端点,Prometheus可以定时获取并存储指标信息。

  • 修改项目的pom.xml文件,添加actuator及micrometer依赖;
1
2
3
4
5
6
7
8
9
10
11
xml复制代码<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 集成micrometer,将监控数据存储到prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
  • 修改应用配置文件application.yml,通过actuator暴露监控端口/actuator/prometheus;
1
2
3
4
5
6
7
8
9
yaml复制代码management:
endpoints:
web:
exposure:
# 暴露端点`/actuator/prometheus`
include: 'prometheus'
metrics:
tags:
application: ${spring.application.name}
  • 在监控SpringBoot应用之前,我们需要先运行一个SpringBoot应用,使用如下命令运行即可;
1
2
3
4
5
bash复制代码docker run -p 8088:8088 --name mall-tiny-grafana \
-v /etc/localtime:/etc/localtime \
-v /mydata/app/mall-tiny-grafana/logs:/var/logs \
-e TZ="Asia/Shanghai" \
-d mall-tiny/mall-tiny-grafana:1.0-SNAPSHOT
  • 修改Prometheus的配置文件prometheus.yml,创建一个任务定时扫描actuator暴露的指标信息,这里需要注意下,由于SpringBoot应用运行在Docker容器中,需要使用docker inspect mall-tiny-grafana |grep IPAddress来获取容器IP地址;
1
2
3
4
5
6
7
8
9
10
11
12
yaml复制代码scrape_configs:
# 采集任务名称
- job_name: 'mall-tiny-grafana'
# 采集时间间隔
scrape_interval: 5s
# 采集超时时间
scrape_timeout: 10s
# 采集数据路径
metrics_path: '/actuator/prometheus'
# 采集服务的地址
static_configs:
- targets: ['172.17.0.5:8088']
  • 我们可以通过Prometheus的可视化界面,来确定Prometheus是否能获取到指标信息;

  • 同样,我们可以从仪表盘市场导入仪表盘,访问地址:grafana.com/grafana/das…

  • 导入成功后就可以在Grafana中看到SpringBoot实时监控信息了,果然够炫酷!

总结

通过对Grafana的一波实践,我们可以发现,使用Grafana来进行数据可视化的过程是这样的:首先我们得让被监控方将指标信息暴露出来,然后用Prometheus定时获取并存储指标信息,最后将Prometheus配置为Grafana的可视化数据源。

参考资料

  • Grafana官方文档:grafana.com/docs/grafan…
  • node-exporter的使用:prometheus.io/docs/guides…

项目源码地址

github.com/macrozheng/…

本文 GitHub github.com/macrozheng/… 已经收录,欢迎大家Star!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

可视化大屏可真是太 beautiful 了!!!

发表于 2021-07-14

一名致力于在技术道路上的终身学习者、实践者、分享者,一位忙起来又偶尔偷懒的原创博主,一个偶尔无聊又偶尔幽默的少年。

欢迎各位掘友们微信搜索「杰哥的IT之旅」关注!

原文链接:“罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?

去年4月1日,罗永浩直播带货首秀在抖音进行,这场直播累计观看人数超过3000万人,销售额最终超1.1亿。接着网上发布了对于这场直播数据的“可视化大屏展示”,很多人都好奇这个是用什么做的,今天就带大家做一个类似于下图的可视化大屏。

图片

上图是罗永浩直播数据的可视化大屏展示,下图是本文我们要做的可视化大屏展示,先来给大家看一个视频!如出一辙,有兴趣的可以跟着我操作一遍。

一、ERP系统(企业资源规划)

将资源配比运用到企业身上,叫做“ERP系统”,全称是“EnterPrice Recourse Planning”,以某想公司为例来说,大家都知道它是做电脑的。

1) 采购管理:像 CPU、主板、芯片他们都做不了,于是需要采购。

2) 生产管理:主要是看一下工艺,以及合格率等。

3) 库存管理:根据市场的需求,哪些产品需要多库存一些,哪些需要少库存一些,保证库存供给均衡。

4) 销售管理:销售分线上的和线下的。

5) 物流管理

6) 财务管理:像进账、出账、预算等。

7) 人力管理:公司员工关系。

8) 企业文化:不同公司有着不同的企业文化。

二、HRP

将 ERP 系统运用到医疗行业,就成了“HRP”,全称是“Hospital Resource Planning”,又叫“医院资源规划”。

三、项目背景及目的

本文基于某医院一些数据,为院长制作一个“驾驶舱”,帮助他了解医院的运营情况及其存在的问题,并针对这些问题进行诊断,达到资源配比合理化。

四、项目需求

我们从上面的目的可以知道,本文就是要为院长做一个可视化大屏,帮助他做决策,既然是帮院长做,那么我们就要知道,作为一个院长,主要关注哪些东西,他所关注的,就是我们要做的。下面我们简要列举以下几个:

  • 各科室的人员配比情况,像医生、护士、患者。
  • 各科室的就诊情况,门诊、住院(病床)
  • 各科室的收入;

五、数据表的介绍

图片

六、软件实现

6.1 新建一个决策报表,添加一个背景

图片

6.2 连接数据库:hospital_hrp

图片

6.3 标题及科室列表图的制作

6.3.1 新建数据连接,导入数据源

图片

6.3.2 导入报表块儿:添加一个标题(资源配比决策大屏)

图片

6.3.3 点击上述编辑,进行如下操作

图片

6.3.4 保存后,进行效果预览

图片

6.3.5 再次导入一个报表块儿,用于制作科室列表图

图片

6.3.6 点击上述编辑后,完成如下操作

图片

6.3.7 调整两个报表块儿的间距后,保存并进行效果展示

图片

6.4 各科室人员配比分析

  • 国家标配指标:医患比(1:5)、护患比(1:2.5),这个数字是国家标配。
  • 需求1:某科室医、护患比月度趋势分析
  • 需求2:某科室国家标配(占比)和医院现状(占比)的对比分析
6.4.1 需求1:某科室医、护患比月度趋势分析
利用SQL语句,统计不同科室有多少医生、护士
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
csharp复制代码"--------医生人数统计----------"
select
    deptno,deptname,
  count(name) 医生人数
from doctor
group by deptno,deptname

"--------护士人数统计----------"
select
    deptno,deptname,
  count(name) 护士人数
from nurse
group by deptno,deptname

"--------患者不同科室、不同月份的人数统计----------"
select 
   deptno,in_month,
     count(name) 患者人数
from hospital
group by deptno,in_month

结果如下:

图片

进行表连接,得到我们用于绘图的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
csharp复制代码select 
       a.deptname,in_month,
       (患者人数/医生人数) as 医患比,
       (患者人数/护士人数) as 护患比
from
(select
       deptno,deptname,
  count(name) 医生人数
from doctor
group by deptno,deptname) a,

(select
  deptno,deptname,
    count(name) 护士人数
from nurse
group by deptno,deptname) b,


(select 
     deptno,in_month,
   count(name) 患者人数
from hospital
group by deptno,in_month) c
where a.deptno = b.deptno and a.deptno = c.deptno

结果如下(截取部分结果如下):

图片

在帆软中,利用上述SQL语句,得到最终的绘图数据

图片

插入一个折线图

图片

点击上述的编辑后,进行数据源的绑定

图片

进行标题的设置(很关键)

由于我们未来需要进行联动效果的设置,也就是说,你选择哪一个科室,标题就要显示哪一个科室,这一步很关键。这里强调一下,下面的叙述中,就不再介绍了。

图片

添加两条警戒线(也就是我们常说的参考线)

我们知道国家配比关系:医患比(1:5)、护患比(1:2.5),因此以2.5和5为准,各做一条参考线,可以将医院的现状和这条参考线,进行清楚的对比。

图片

注意:同理,我们可以再次添加一个护患比趋势线,操作步骤一摸一样,这里不做演示。

保存操作后,进行效果展示

注意:每次做完每个操作后,最好保存一下。

图片

问题来了:此时的科室表,与下方的折线图并不能进行联动,因此怎么办呢?

科室表与折线图联动效果的设置

首先,我们编辑这个“科室表”

图片

当出现如下界面,完成如下操作。

图片

当出现如下界面,完成如下操作。

图片

保存后,进行效果展示。

图片

6.4.2 需求2:某科室国家标配(占比)和医院现状(占比)的对比分析
利用SQL语句,得到“某科室国家标配占比”
1
2
sql复制代码SELECT * FROM `normalconfig`
where deptname='${科室名称}'

操作如下:

图片

插入一个饼图,并选择为“圆环图-旧版本”,并选择为立体圆环图

图片

进行数据源的绑定

图片

进行标题的设置(为什么设置,上述已经进行了说明)

图片

注意:我其实还进行了其它参数的设置,很简单,这里我们就不截图演示

接着,我们再次进行“科室表”与圆环图,联动效果的设置

首先,我们编辑这个“科室表”

图片

当出现如下界面,完成如下操作。

图片

当出现如下界面,完成如下操作。

图片

保存后,进行效果展示。

图片

同理,我们可以做出,“某科室自己医院现状标配占比”,由于大部分步骤一样,这里就只演示不同的步骤。只有“绑定数据源”这一步操作不同。

图片

但是此时并不行,为什么呢?因为我们左右两个圆环图的图例并不是一一对应。此时,我们将“国家标配”这个圆环图,今行字段的调整。

图片

保存后,进行效果演示。

图片

6.5 就诊情况分析

  • 需求1:某科室现有及标配病床对比分析(条形图)
  • 需求2:病床利用率
  • 需求3:某科室病床利用率月度趋势分析
6.5.1 需求1:某科室现有及标配病床对比分析
利用SQL语句,统计现有及标配病床数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
csharp复制代码select 
       a.deptname,
       标配,
       现有,
       (标配-现有) as 差值
from 
(select 
       deptno,deptname,bednum as 标配
 from normalconfig
) a,
(select 
  deptno,deptname,bednum as 现有
 from department
 ) b
where a.deptno = b.deptno

结果如下(截取部分如下):

图片

在帆软中,利用上述SQL语句,得到最终的绘图数据

图片

插入一个条形图,并进行数据的绑定

图片

进行标题的设置

图片

动态闪烁效果展示:特效功能

当某个值出现了负数,就标红。

图片

接着,我们再次进行“科室表”与条形图,联动效果的设置

首先,我们编辑这个“科室表”

图片

当出现如下界面,完成如下操作。

图片

当出现如下界面,完成如下操作。

图片

保存后,进行效果展示。

图片

6.5.2 需求2:病床利用率

病床利用率 = 一个月的患者人数 / 病床数(0.7-0.9表示还不错)

利用SQL语句,统计病床利用率达标的科室
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
csharp复制代码select
        deptname,
        in_month,
        round((患者人数/bednum),2) as 病床利用率
from
(select
     *
from department) a,

(select 
    deptno,in_month,
    count(name) as 患者人数
from hospital
group by deptno,in_month) b
where a.deptno = b.deptno
and (患者人数/bednum) >= 0.7 and (患者人数/bednum) <= 0.9

结果如下:

图片

在帆软中,利用上述SQL语句,得到最终的绘图数据

图片

插入一个条形图,并进行数据的绑定

图片

保存后,进行效果展示

图片

6.5.3 需求3:某科室病床利用率月度趋势分析
利用SQL语句,统计现有及标配病床数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
csharp复制代码select
       deptname,
       in_month,
       round((患者人数/bednum),2) as 病床利用率
from
(select
    *
from department) a,

(select 
   deptno,in_month,
   count(name) as 患者人数
from hospital
group by deptno,in_month) b
where a.deptno = b.deptno

结果如下(截取部分如下):

图片

在帆软中,利用上述SQL语句,得到最终的绘图数据

图片

插入一个条形图,并进行数据的绑定

图片

进行标题的设置

图片

添加一个参考线:0.85

图片

接着,我们再次进行“科室表”与条形图,联动效果的设置

首先,我们编辑这个“科室表”

图片

当出现如下界面,完成如下操作。

图片

当出现如下界面,完成如下操作。

图片

保存后,进行效果展示。

图片

6.6 收入情况分析

  • 需求1:某科室住院收入月度趋势分析
  • 需求2:病床利用率
6.6.1 需求1:某科室住院收入月度趋势分析
利用SQL语句,统计现有及标配病床数
1
2
3
4
5
6
7
8
9
10
csharp复制代码select 
       deptname,in_month,收入
from 
(select 
       deptno,in_month,
       round(sum(lastincome),2) as 收入
from hospital
group by deptno,in_month) a,
(select * from department) b
where a.deptno = b.deptno

结果如下(截取部分如下):

图片

在帆软中,利用上述SQL语句,得到最终的绘图数据

图片

插入一个条形图,并进行数据的绑定

图片

进行标题的设置

图片

接着,我们再次进行“科室表”与条形图,联动效果的设置

首先,我们编辑这个“科室表”

图片

当出现如下界面,完成如下操作。

图片

当出现如下界面,完成如下操作。

图片

保存后,进行效果展示。

图片

推荐阅读

数据分析之 AB testing 实战(附 Python 代码)

Python 自动化办公之”你还在手动操作“文件”或“文件夹”吗?”

最详细的 Python 结合 RFM 模型实现用户分层实操案例!

利用 Python 进行多 Sheet 表合并、多工作簿合并、一表按列拆分


原创不易,如果你觉得这篇文章对你有点用的话,麻烦你为本文点个赞、评论或转发一下,因为这将是我输出更多优质文章的动力,感谢!

对了,掘友们记得给我点个免费的关注哟!防止你迷路下次就找不到我了。

我们下期再见!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

pandas系列之txt文件和sql文件的基本操作 1导入

发表于 2021-07-14

1.导入txt文件

本文所使用到的test.txt的内容如下:

image-20210713232357891.png

使用read_table()方法导入txt文件

1
2
3
4
python复制代码import pandas as pd

df = pd.read_table(r'C:\Users\admin\Desktop\test.txt')
print(df)

result:

1
2
3
复制代码  我是李华。 今天本来留下班里十几个人做大扫除结果他们都跑了,只留下了我一个人干完了所有活。 回家的路上真不巧又下了雨,
0 雨太大把我自行车前轮胎上的辐条都冲走了,我非常生气。 为了报复今天发生的一切,我骑着钢圈回到...
1 并把教室里的椅子都扔了出去。

该方法是将利用分隔符分开的文件导入DataFrame的通用函数。不仅可以导入.txt文件,也可以导入.csv文件。

1
2
python复制代码df = pd.read_table(r'C:\Users\admin\Desktop\中文\数据分析测试表.csv')
print(df)

result:

1
2
3
4
5
6
复制代码   区域,省份,城市
0 东北,辽宁,大连
1 西北,陕西,西安
2 华南,广东,深圳
3 华北,北京,北京
4 华中,湖北,武汉

read_table()方法的其他参数用法和read_csv()方法基本一致,再此不再赘述。

2.导入sql文件

2.1 安装依赖库pymysql

python连接MySQL要用到pymysql,需要手动进行安装。
image-20210713233131704.png

1
2
3
4
5
6
7
8
9
10
11
ini复制代码import pandas as pd
import pymysql

con = pymysql.connect(host='127.0.0.1', # 数据库地址,本机为127.0.0.1或localhost
user='root', # 用户名
password='123456', # 密码
db='test', # 数据库名
charset='utf-8') # 数据库编码,一般为utf-8
sql = "select * from employees"
df = pd.read_sql(sql, con)
print(df)

此时报错

image-20210713234105887.png
修改charset=’utf8’后错误解决:

1
2
3
4
5
6
7
8
ini复制代码con = pymysql.connect(host='127.0.0.1',  # 数据库地址,本机为127.0.0.1或localhost
user='root', # 用户名
password='123456', # 密码
db='test', # 数据库名
charset='utf8') # 数据库编码,一般为utf-8
sql = "select * from employees"
df = pd.read_sql(sql, con)
print(df)

result:

1
2
3
4
yaml复制代码   eID NAME sex       birth jobs      firJob    hiredate
0 1 张三 男 1990-06-21 教师 2005-06-20 2009-08-26
1 2 鲁怀德 男 2004-06-29 工人 2018-08-01 2021-01-06
2 3 赵燕妮 女 1994-07-06 售货员 2004-09-21 2019-05-23

这里提供数据库查询结果作为比对:

image-20210713234431680.png
注:python使用pymysql与MySQL交互时,编码方式只能写成utf8,不要习惯性地写成utf-8

3.小结

导入数据主要用到pandas里的read_x()方法,x表示待导入文件的格式

除了之前介绍的导入.xlsx文件的read_excel(),导入.csv文件的read_csv(),导入txt的read_table(),导入sql文件的read_sql()之外,还有一些其他方法在此列出。这些使用到的不多,在此不做深入说明,。后面如有使用到再进行更新。

1
2
3
4
5
6
7
8
scss复制代码pd.read_xml()
pd.read_html()
pd.read_json()
pd.read_clipboard()
pd.read_feather()
pd.read_fwf()
pd.read_gbq()
pd.read_orc()

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…608609610…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%