能力说明:
精通JVM运行机制,包括类生命、内存模型、垃圾回收及JVM常见参数;能够熟练使用Runnable接口创建线程和使用ExecutorService并发执行任务、识别潜在的死锁线程问题;能够使用Synchronized关键字和atomic包控制线程的执行顺序,使用并行Fork/Join框架;能过开发使用原始版本函数式接口的代码。
前言:前段时间,自己在玩docker,就在自己的docker中run了一个nacos,以给自己后续做demo项目使用。放置了很长时间也没再打开看看啥的。近期在做demo,用到了nacos,就突然发现nacos中的用户列表中多了几个用户,第一反应就是,被人搞了。然后,把用户删了,没再搭理。第二天,打开nacos又看了一下,发现多了一个myworld的用户,这一下子,就让我好好看看nacos了~~~一、不登录状态下的操作1.1 查询用户列表http://localhost:8848/nacos/v1/auth/users?pageNo=1&pageSize=1001.2 随意添加一个用户http://localhost:8848/nacos/v1/auth/users?username=test&password=testUser-Agent:Nacos-Server 该参数为nacos 白名单数据。1.3 原因出现上面的问题,是因为没有开启nacos权限认证nacos.core.auth.enabled=false二、问题解决2.1 漏洞概述https://github.com/alibaba/nacos/issues/45932.2 Nacos给出的修复建议关于Nacos身份验证漏洞修复建议2.3 服务开启鉴权关于安全问题Nacos是一个内部微服务组件,需要在可信的内网中运行,不可暴露在公网环境,防止带来安全风险。结合Nacos官网中的服务端如何开启鉴权去解决# 主要是将这个参数开启 nacos.core.auth.enabled=true # 关闭使用User-Agent判断服务端请求并放行鉴权的功能 nacos.core.auth.enable.userAgentAuthWhite=false # 配置自定义身份识别的key和value nacos.core.auth.server.identity.key=example nacos.core.auth.server.identity.value=example # 同时,建议自定义用于生成JWT令牌的密钥 nacos.core.auth.default.token.secret.key我主要是开启了这个参数nacos.core.auth.enabled=true其实,Nacos的文档(服务端如可开启鉴权)中给出了关于非Docker环境和Docker环境的开启建议
我们在打包时通常会加上目标环境,即${spring.profiles.active}亦或者加上端口号,即${server.port}亦或者想知道打包的文件日期,即timestamp想要实现需要,大多是通过build中的plugin去实现读取properties文件<build> <finalName>${artifactId}_${version}_${server.port}_${spring.profiles.active}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- 保证application.properties中的变量能够在maven中读取,用${xx}占位替换 --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>properties-maven-plugin</artifactId> <version>1.0.0</version> <executions> <execution> <phase>initialize</phase> <goals> <goal>read-project-properties</goal> </goals> <configuration> <files> <file>src/main/resources/application.properties</file> </files> </configuration> </execution> </executions> </plugin> </plugins> </build>读取yml文件项目地址:https://github.com/ozimov/yaml-properties-maven-plugin在项目中也给出了使用姿势<build> <finalName>${artifactId}_${version}_${server.port}_${spring.profiles.active}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- 保证application.yml中的变量能够在maven中读取,用${xx}占位替换 --> <plugin> <groupId>it.ozimov</groupId> <artifactId>yaml-properties-maven-plugin</artifactId> <version>1.1.3</version> <executions> <execution> <phase>initialize</phase> <goals> <goal>read-project-properties</goal> </goals> <configuration> <files> <file>src/main/resources/application.yml</file> </files> </configuration> </execution> </executions> </plugin> </plugins> </build>spring-boot-maven-plugin 版本为2.1.2 下有效, 2.1.4下无效添加timestamp<build> <finalName>${artifactId}_${version}_${timestamp}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>buildnumber-maven-plugin</artifactId> <version>1.4</version> <configuration> <!-- 这里指定timestamp的格式 --> <timestampFormat>yyyyMMdd_HHmm</timestampFormat> </configuration> <executions> <execution> <goals> <goal>create-timestamp</goal> </goals> </execution> </executions> <inherited>false</inherited> </plugin> </plugins> </build>
项目背景:项目中写了很多个CommandLineRunner,导致在本地启动项目的时候就会很慢,甚至更会有意想不到的报错出现;一个个的去注释掉的话,又会很麻烦,于是就想到了指定不扫描某些包@ComponentScan(excludeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.demo.dev.*") ,@ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.demo.test.*") }) @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }在不扫描这些个CommandLineRunner类之后,项目启动上也会变得很快。那么问题来了,这些CommandLineRunner是在Spring中的哪个阶段加入的,给自己的学习留个坑,学习了Spring源码之后再来填坑
使用SSM作为java的开发框架,总少不了在xxMapper.xml手写sql,那么也就少不了、标签的书写1. 字符串类型的判断1.1 惯用写法<if test="userType != null and userType != ''"> and user_type = #{userType} </if> 长久以来,对于变量的判断,大家都养成了上面的习惯实际上,这种写法对于字符串类型的userType没有任何问题1.2 字符串类型的值判断下面是在开发的过程中的一种错误写法<if test="userType != null userType == '1'"> and use_type = #{userType} </if>那么解决方案是以下将单引号放在最外面,将双引号作为字符串的值判断<if test='userType != null userType == "1"'> and use_type = #{userType} </if>使用对象的toString()方法<if test="userType != null userType == '1'.toString()"> and use_type = #{userType} </if>2. number类型判断若果userType是数字类型的话,当userType=0时就不能进入if条件的判断所以此时只需要判断不等于null即可,或者userType!=0<if test="userType != null"> and user_type = #{userType} </if>在看过mybatis的源码之后,再进行更文,有理有据,对这些不能使用的写法来进行解释
一、作用单个实例保证我们的实例对象在整个应用程序中只有一个实例二、创建方式五种实现方式2.1 饿汉式在类加载的时候立即初始化,并且创建单例对象它绝对线程安全,在线程出现以前就实例化了,不可能存在访问安全问题优点:不加任何锁,执行效率比较高,用户体验比懒汉单例模式好缺点:浪费内存,不管用不用都占着内存public class HungrySingleton { // 直接实例化方式 // private static final HungrySingleton hungrySingleton = new HungrySingleton(); private static final HungrySingleton hungrySingleton; // 静态块单例模式 static { hungrySingleton = new HungrySingleton(); } private HungrySingleton(){} private static HungrySingleton getInstance(){ return hungrySingleton; } }2.2 懒汉式在外部调用的时候才进行实例化,相比较饿汉式避免了资源浪费在单线程情况下,比较友好。多线程情况下,会存在线程安全问题public class LazySimpleSingleton { private LazySimpleSingleton() { } private static LazySimpleSingleton lazySimpleSingleton = null; public static LazySimpleSingleton getInstance() { if (null == lazySimpleSingleton) { lazySimpleSingleton = new LazySimpleSingleton(); } return lazySimpleSingleton; } }2.3 双重检测锁(DCL)基于懒汉式的线程安全问题,有了双重校验锁public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; /** * 私有化构造方法,防止直接通过new实例化 */ private LazyDoubleCheckSingleton() { } public static LazyDoubleCheckSingleton getInstance() { if (lazyDoubleCheckSingleton == null) { synchronized (LazyDoubleCheckSingleton.class) { if (lazyDoubleCheckSingleton == null) { // 1.分配内存空间 2.执行构造方法,实例化对象 3.把这个对象赋给这个空间 // 不加volatile关键字,会造成指令重排,1,3,2 lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); } } } return lazyDoubleCheckSingleton; } }2.4 静态内部类public class StaticSingleton { public static class InnerStaticSingleton { /** * 声明外部类型的静态常量 */ public static final StaticSingleton instance = new StaticSingleton(); } private StaticSingleton() { } public StaticSingleton getInstance() { return InnerStaticSingleton.instance; } }2.5 枚举类型public enum EnumSingleton { INSTANCE; public void handleMethod(){ // 业务处理 } }综上的五种写法,大多都是在考虑着线程安全问题2.6 反射爆破问题私有的构造器,可以通过反射去破坏。在私有构造器中进行判断,进而抛出异常。public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { LazySimpleSingleton instance = LazySimpleSingleton.getInstance(); Class<LazySimpleSingleton> clazz = LazySimpleSingleton.class; Constructor<LazySimpleSingleton> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); LazySimpleSingleton instance1 = constructor.newInstance(); System.out.println(instance); System.out.println(instance1); } // 结果 com.example.validated.design.singleton.LazySimpleSingleton@6d5380c2 com.example.validated.design.singleton.LazySimpleSingleton@45ff54e62.7 序列化与反序列化破坏单例LazySimpleSingleton要实现Serializable序列化接口public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { LazySimpleSingleton instance = LazySimpleSingleton.getInstance(); Class<LazySimpleSingleton> clazz = LazySimpleSingleton.class; Constructor<LazySimpleSingleton> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); LazySimpleSingleton instance1 = constructor.newInstance(); System.out.println(instance); System.out.println(instance1); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("d:/tools/a.txt")); outputStream.writeObject(instance); outputStream.flush(); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("d:/tools/a.txt")); LazySimpleSingleton instance2 = (LazySimpleSingleton) inputStream.readObject(); inputStream.close(); System.out.println(instance); System.out.println(instance2); } // 结果 com.example.validated.design.singleton.LazySimpleSingleton@6d5380c2 com.example.validated.design.singleton.LazySimpleSingleton@45ff54e6 com.example.validated.design.singleton.LazySimpleSingleton@6d5380c2 com.example.validated.design.singleton.LazySimpleSingleton@5e265ba4我们需要重写readResolve()方法private Object readResolve() { return lazySimpleSingleton; }结果:com.example.validated.design.singleton.LazySimpleSingleton@6d5380c2 com.example.validated.design.singleton.LazySimpleSingleton@45ff54e6 com.example.validated.design.singleton.LazySimpleSingleton@6d5380c2 com.example.validated.design.singleton.LazySimpleSingleton@6d5380c2说明:readResolve()方法是基于回调的,反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要再创建新的对象。 三、应用在框架中看到的单例模式Spring中的Bean对象,默认是单例模式相关的工厂对象都是单例,如:Mybatis中的SqlSessionFactory,Spring中BeanFactory保存相关配置信息的都是单例,如:Mybatis中的Configuration对象,SpringBoot中的各个xxxAutoConfiguration对象应用程序的日志应用,一般都会通过单例来实现数据库的连接池的设计也是单例模式
一、安装docker1.1 centos 7使用yum安装# Sep 1: 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3: 更新并安装 Docker-CE sudo yum makecache fast sudo yum -y install docker-ce # Step 4: 开启Docker服务 sudo service docker start # 查看vesion docker version1.2 配置镜像加速器阿里云容器镜像服务:https://cr.console.aliyun.com/cn-shanghai/instances/mirrors关于镜像加速器使用加速器可以提升获取Docker官方镜像的速速sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["加速器地址"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker1.3 简单操作-运行一个nginx# 搜索nginx的镜像 docker search nginx # 拉取latest版本的nginx镜像 docker pull nginx:latest # 查看本地存储的镜像 docker images # 运行容器 docker run --name nginx -p 80:80 -d nginx # 进入容器内部 docker exec -it nginx bash1.3.1 命令参数说明--name nginx指定容器名称为nginx-p 80:80端口映射,将本地80端口映射到容器内部的80端口-d 设置容器在后台一直运行nginx 指定运行的镜像1.3.2 其他参数-v 挂载映射目录二、部署nginx2.1 复制容器内的nginx的配置文件docker cp nginx:/etc/nginx /data/nginx复制过来的结构其中目前用到的有nginx.conf配置文件和conf.d文件夹下的default.conf默认配置文件说明文件挂载路径容器nginx路径nginx相关nginx.conf和conf.d/data/nginx/etc/nginxhtml路径html文件夹/data/nginx/html/usr/share/nginx/html日志文件log文件夹/data/nginx/logs/var/log/nginx2.1.1 拓展docker cp命令// 将容器内文件复制到宿主机中docker cp [容器名称]:/目录 /目录// 将宿主中的文件复制到容器内部docker cp /目录 [容器名称]:/目录2.2 创建其他挂载目录mkdir -p /data/nginx/{html,logs}2.3 修改default.conf文件server { #端口号 listen 80; #定义使用 localhost 访问 server_name localhost; #charset koi8-r; #access_log /var/log/nginx/host.access.log main; location / { #根目录位置 root /usr/share/nginx/html; #index 文件位置 index test.html; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} }2.4 在html文件夹下创建test.html网页<html> <head> <title>Mynginx</title> </head> <body> <h1> Hello Nginx!!! </h1> </body> </html>2.5 删除之前创建的容器docker stop nginx docker rm nginx2.6 创建新的容器docker run --name nginx -d -p 80:80 \ -v /data/nginx/html:/usr/share/nginx/html \ -v /data/nginx/logs:/var/log/nginx \ # 上面因为已经将nginx容器中的nginx文件夹整个cp过来的 # 这里将挂载映射整个文件夹 # 为后面实现ssl做铺垫 -v /data/nginx:/etc/nginx \ nginx2.7 不停止nginx更新配置文件2.7.1 第一种# 进入到nginx容器的内部 docker exec -it nginx bash # 测试配置文件是否有问题,显示successful证明配置文件没有问题 nginx -t # 更新 nginx -s reload2.7.2 第二种第一种是进入到容器内部,有些繁琐下面将两者整合# 后面的nginx -t是nginx的命令 docker exec [容器名称(nginx)] nginx -t # 后面的nginx -s reload是nginx的命令 docker exec [容器名称(nginx)] nginx -s reload三、配置ssl3.1 购买阿里云的免费证书阿里云每年可以有20张免费的证书,即可以为20个域名提供sslhttps://yundun.console.aliyun.com/?p=cas#/certExtend/free3.2 创建证书关联域名解析域名解析:https://dns.console.aliyun.com/#/dns/domainList完成签发之后,状态这一栏会是这个样子继续点击“下载”,下载我们需要的服务器证书,这里选择nginx3.3 配置证书3.3.1 创建证书存放目录mkdir /data/nginx/ssl3.3.2 上传证书将下载的证书解压,上传到这个存放目录中会有xxx.pem和xxx.key这两个文件由于我们在运行容器的时候已经挂载了-v /data/nginx:/etc/nginx这个时候我们进入到容器内部docker exec -it nginx bash在/etc/nginx/ssl文件夹中发现这两个文件cd /etc/nginx/ssl ls -lh3.3.3 查看挂载配置docker inspect nginx3.4 修改配置文件配置方式可以使用下载证书旁边的帮助,在Nginx或Tengine服务器上安装证书#以下属性中,以ssl开头的属性表示与证书配置有关。 server { listen 443 ssl; #配置HTTPS的默认访问端口为443。 #如果未在此处配置HTTPS的默认访问端口,可能会造成Nginx无法启动。 #如果您使用Nginx 1.15.0及以上版本,请使用listen 443 ssl代替listen 443和ssl on。 server_name yourdomain; root html; index index.html index.htm; ssl_certificate /etc/nginx/ssl/cert-file-name.pem; ssl_certificate_key /etc/nginx/ssl/cert-file-name.key; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #表示使用的加密套件的类型。 ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; #表示使用的TLS协议的类型,您需要自行评估是否配置TLSv1.1协议。 ssl_prefer_server_ciphers on; location / { root html; #Web网站程序存放目录。 index index.html index.htm test.html; } } # 注意 以下代码片段需要放置在nginx.conf文件中server {}代码段后面,即设置HTTP请求自动跳转HTTPS后,nginx.conf文件中会存在两个server {}代码段。 server { listen 80; server_name yourdomain; #需要将yourdomain替换成证书绑定的域名。 rewrite ^(.*)$ https://$host$1; #将所有HTTP请求通过rewrite指令重定向到HTTPS。 location / { index index.html index.htm test.html; } }3.5 刷新配置文件docker exec nginx nginx -t docker exec nginx nginx -s reload访问配置的域名,就会看到自己创建的界面了由于我们没有将/usr/share/nginx/html中的原始默认页面cp过来,所以不会看到熟悉的nginx的默认页面。
一、第一个问题当excel导入的数据量比较大或者后台操作数据的逻辑耗时比较长,这个时候我们会考虑异步导入文件异步方法的实现有着几种实现方式,这里通过指定异步线程池实现的,即@Async("线程池名称")标注异步方法。然而,在经过测试时发现,该标注的注解也都标注了,但是不能实现异步效果。几经波折,发现异步方法可以调用非异步方法,是可以实现异步效果;而先是非异步方法调用异步方法,这样就会失效。这里所说的是在同一个java类里面。而在不同的java类里面,就不存在上面的问题二、第二个问题使用了异步方法,通过Controller接收文件参数,service层进行处理,这时Controller已经将执行成功的结果返回,剩下的就是service层中去进行解析入库。结果,万万没想到,在service中报了一个错,就找不到文件java.nio.file.NoSuchFileException: D:\UserData\Temp\undertow.1407321862395783323.8400\undertow4517937229384702645upload2.1 Controller中的方法@PostMapping("/test") public R<String> test(MultipartFile file) { testService.test(file); return R.success("导入成功"); }2.2 service中的方法@Async("asyncImportExecutor") public void test(MultipartFile file) { try { EasyExcelUtil.read(file.getInputStream(), Test.class, this::executeImport) .sheet().doRead(); } catch (Exception ex) { log.error("[test]异步导入异常:", ex); } }看到这个异常NoSuchFileException后就一脸懵逼,测试是通过postman进行的,就开始怀疑postman的问题,已经排查后postman、路径啥的都没有问题,异步调用的流程也是ok的然后就想这个异常的提示,就是找不到文件,根据打印出来的日志去本地也确实没有找到。后来就有了下面的写法2.3 改进Controller中的方法@PostMapping("/test") public R<String> test(MultipartFile file) { try { testService.test(file.getInputStream()); } catch (IOException e) { log.error("[test]异常日志:", e); return R.fail("导入失败"); } return R.success("导入成功"); }2.4 改进Service中的方法@Async("asyncImportExecutor") public void test(InputStream file) { try { EasyExcelUtil.read(file, Test.class, this::executeImport) .sheet().doRead(); } catch (Exception ex) { log.error("[test]异步导入异常:", ex); } }这样就不报错了。。。后来就进行debug调试,发现临时文件是controller层中的MultipartFile对象生成,一直在写同步方法,也没注意这个MultipartFile对象会生成临时文件。后来发现这个controller中返回结果后,临时文件也就没有了。2.5 错误总结因为使用的是异步方法,这样就会有一个主线程和一个异步线程。在上传文件后会形成MultipartFile类型的实例,同时生成临时文件,此时是在主线程中。MultipartFile的实例交给异步线程处理后,该临时文件会被springboot(spring)销毁,在异步线程中去getInputStream就会出现上面的异常。而在下面的写法是将文件流作为了入参,就不会产生找不到文件的情况。三、第三个问题切面日志打印3.1 问题描述最近做的需求,有上传文件参数的情况,即 MultipartFile 的情况而且大家的系统框架中也有通过类似 @SysLog 注解切面打印入参日志的需要这时存在 MultipartFile 类型的参数的时候,估计就会遇见这种类似的情况java.io.FileNotFoundException:MultipartFile resource [file] cannot be resolved to URLMultipartFile resource [file] cannot be resolved to absolute fileMultipartFile resource [file] cannot be resolved to absolute file path可能这个 MultipartFile 这是一个特殊对象的存在吧 ~ ~ ~通过 Jackson 对 javaBean 序列化 MultipartFile 字段成json字符串的时候,有这个问题通过 JSONObject 对 javaBean 序列化 MultipartFile 字段成 json 字符串的时候,也会有类似的问题3.2 借鉴,特殊判断// 请求参数处理 final Map<String, Object> paraMap = new HashMap<>(16); if (value instanceof MultipartFile) { MultipartFile multipartFile = (MultipartFile) value; String name = multipartFile.getName(); String fileName = multipartFile.getOriginalFilename(); paraMap.put(name, fileName); }但是这种是针对这种情况的解决@GetMapping("/test") @ResponseBody public void test(Query query, MultipartFile file) { ........... return; }像这种情况的,依然无法解决,只能是 兵来将挡水来土掩 了@GetMapping("/test") @ResponseBody public void test(Query query) { ........... return; } public class Query { private MultipartFile file; private Integer page; private Integer size; // 省略Get、Set.......... }这个时候就只能将参数对象中的参数Field进行逐一 instanceof MultipartFile,万一出现套娃的情况,就~ ~ ~3.3 最后要么将这种情况的不再进行入参的打印要么就进行特殊的判断
一、参数校验的实现1. 以前参数的校验大都通过编码的方式实现,如 Hutool 实现StrUtil.isNotEmpty(arg); StrUtil.isNotNull(arg); StrUtil.isNotBlank(arg); ……2. 新成员 @Validated 和 @Valid关于两者的区别@Validated是org.springframework.validation.annotation.Validated,支持group分组@Valid是javax.validation.Valid实际使用时,可以使用两者的嵌套更多的高阶使用姿势,可参考:https://segmentfault.com/a/1190000022605819?utm_source=tag-newest二、自定义一个 @Validated 参数注解1. 定义注解package com.example.validated.validation; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; /** * 自定义身份证校验注解 * * @author miaoxm * @date 2021/12/24 */ @Documented @Target({ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = IdentityCardNumberValidator.class) public @interface IdentityCardNumber { String message() default "身份证号码不合法"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }2. 注解校验实现package com.example.validated.validation; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; /** * 真正的校验逻辑 * * @description * 1.必须实现 ConstraintValidator 接口 * 2.实现了 ConstraintValidator 接口后即使不进行 Bean 配置,spring 容器也会将这个类进行 Bean 管理 * 3.可以在实现了 ConstraintValidator 接口的类中依赖注入其他的 Bean * 4.实现了 ConstraintValidator 接口后必须重写 initialize 和 isValid 这两个方法 * - 其中 initialize 方法主要来进行初始化,通常用来获取自定义注解的属性 * - 其中 isValid 方法主要进行校验逻辑,返回true表示校验通过;返回false表示校验失败 * @author miaoxm * @date 2021/12/24 */ public class IdentityCardNumberValidator implements ConstraintValidator<IdentityCardNumber, Object> { @Override public void initialize(IdentityCardNumber constraintAnnotation) { ConstraintValidator.super.initialize(constraintAnnotation); } @Override public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { return check(o.toString()); } private boolean check(String arg) { // 具体的参数校验实现可以写在这儿 return true; } }三、全局异常处理作用:让校验生效,即参数校验时如果不合法就会抛出异常,我们就可以在全局异常中捕获拦截到,然后进行逻辑处理之后再返回@RestControllerAdvice public class MyGlobalExceptionHandler { …… }
武林头条-建站小能手争霸赛2022.7.25-2022.8.5,参与武林头条-建站小能手争霸赛,完成以下比赛即可领取丰厚礼品!第一回合:少林、武当、峨眉三大门派会师完成3个任务,可得20元猫超卡,工作日10点发放,每日限量200份限量,快点行动起来吧!第二回合:群雄逐鹿,争当第一邀请好友完成场景体验,参与PK赢取大奖,最高得iPhone13!根据邀请好友数量进行排名,还有更多大奖等你来拿!立即前往领取好礼:https://developer.aliyun.com/adc/series/activity/wulin
在上一篇文章中总结了装饰器模式,现在将装饰器结合实际应用起来。使用装饰器模式改造slf4j打印json格式日志,以便于日志的分析一、准备装饰器模式得有一个抽象组件Component这里的抽象组件可以直接是org.slf4j.Logger,org.slf4j.LoggerFactory#getLogger(java.lang.Class)可以看作是一个具体组件(Concrete Component)。还缺少装饰器(Decorator)和具体组件(Concrete Decorator)二、实现2.1 装饰器JsonLoggerDecoratorpublic class JsonLoggerDecorator implements Logger { protected final Logger logger; public JsonLoggerDecorator(Logger logger) { this.logger = logger; } @Override public String getName() { return null; } @Override public boolean isTraceEnabled() { return false; } @Override public void trace(String msg) { } @Override public void trace(String format, Object arg) { } @Override public void trace(String format, Object arg1, Object arg2) { } @Override public void trace(String format, Object... arguments) { } @Override public void trace(String msg, Throwable t) { } @Override public boolean isTraceEnabled(Marker marker) { return false; } @Override public void trace(Marker marker, String msg) { } @Override public void trace(Marker marker, String format, Object arg) { } @Override public void trace(Marker marker, String format, Object arg1, Object arg2) { } @Override public void trace(Marker marker, String format, Object... argArray) { } @Override public void trace(Marker marker, String msg, Throwable t) { } @Override public boolean isDebugEnabled() { return false; } @Override public void debug(String msg) { } @Override public void debug(String format, Object arg) { } @Override public void debug(String format, Object arg1, Object arg2) { } @Override public void debug(String format, Object... arguments) { } @Override public void debug(String msg, Throwable t) { } @Override public boolean isDebugEnabled(Marker marker) { return false; } @Override public void debug(Marker marker, String msg) { } @Override public void debug(Marker marker, String format, Object arg) { } @Override public void debug(Marker marker, String format, Object arg1, Object arg2) { } @Override public void debug(Marker marker, String format, Object... arguments) { } @Override public void debug(Marker marker, String msg, Throwable t) { } @Override public boolean isInfoEnabled() { return false; } @Override public void info(String msg) { } @Override public void info(String format, Object arg) { } @Override public void info(String format, Object arg1, Object arg2) { } @Override public void info(String format, Object... arguments) { } @Override public void info(String msg, Throwable t) { } @Override public boolean isInfoEnabled(Marker marker) { return false; } @Override public void info(Marker marker, String msg) { } @Override public void info(Marker marker, String format, Object arg) { } @Override public void info(Marker marker, String format, Object arg1, Object arg2) { } @Override public void info(Marker marker, String format, Object... arguments) { } @Override public void info(Marker marker, String msg, Throwable t) { } @Override public boolean isWarnEnabled() { return false; } @Override public void warn(String msg) { } @Override public void warn(String format, Object arg) { } @Override public void warn(String format, Object... arguments) { } @Override public void warn(String format, Object arg1, Object arg2) { } @Override public void warn(String msg, Throwable t) { } @Override public boolean isWarnEnabled(Marker marker) { return false; } @Override public void warn(Marker marker, String msg) { } @Override public void warn(Marker marker, String format, Object arg) { } @Override public void warn(Marker marker, String format, Object arg1, Object arg2) { } @Override public void warn(Marker marker, String format, Object... arguments) { } @Override public void warn(Marker marker, String msg, Throwable t) { } @Override public boolean isErrorEnabled() { return false; } @Override public void error(String msg) { } @Override public void error(String format, Object arg) { } @Override public void error(String format, Object arg1, Object arg2) { } @Override public void error(String format, Object... arguments) { } @Override public void error(String msg, Throwable t) { } @Override public boolean isErrorEnabled(Marker marker) { return false; } @Override public void error(Marker marker, String msg) { } @Override public void error(Marker marker, String format, Object arg) { } @Override public void error(Marker marker, String format, Object arg1, Object arg2) { } @Override public void error(Marker marker, String format, Object... arguments) { } @Override public void error(Marker marker, String msg, Throwable t) { } }2.2 具体装饰器JsonLoggerimport com.alibaba.fastjson.JSONObject; import org.slf4j.Logger; import java.util.Arrays; public class JsonLogger extends JsonLoggerDecorator { public JsonLogger(Logger logger) { super(logger); } @Override public void info(String msg) { JSONObject jsonObject = this.newJsonObject(); jsonObject.put("message", msg); logger.info(jsonObject.toJSONString()); } @Override public void error(String msg) { JSONObject jsonObject = this.newJsonObject(); jsonObject.put("message", msg); logger.error(jsonObject.toJSONString()); } /** * 重载一个异常的error * * @param e */ public void error(Exception e) { JSONObject jsonObject = this.newJsonObject(); jsonObject.put("name", e.getClass().getName()); String stackTrace = Arrays.toString(e.getStackTrace()); jsonObject.put("stackTrace", stackTrace); logger.error(jsonObject.toJSONString()); } private JSONObject newJsonObject() { return new JSONObject(); } }三、使用Logger对外提供了一个工厂LoggerFactory,这里我们也提供一个JsonLoggerFactoryimport org.slf4j.Logger; import org.slf4j.LoggerFactory; public class JsonLoggerFactory { public static JsonLogger getJsonLogger(Class<?> clazz) { Logger logger = LoggerFactory.getLogger(clazz); return new JsonLogger(logger); } }四、测试类public class JsonLoggerTest { private static final JsonLogger logger = JsonLoggerFactory.getJsonLogger(JsonLoggerTest.class); public static void main(String[] args) { logger.info("info日志"); logger.error("系统异常"); logger.error(new Exception("抛出异常")); } }4.1 执行结果10:30:31.368 [main] INFO com.example.validated.design.decorator.logger.JsonLoggerTest - {"message":"info日志"} 10:30:31.372 [main] ERROR com.example.validated.design.decorator.logger.JsonLoggerTest - {"message":"系统异常"} 10:30:31.372 [main] ERROR com.example.validated.design.decorator.logger.JsonLoggerTest - {"name":"java.lang.Exception","stackTrace":"[com.example.validated.design.decorator.logger.JsonLoggerTest.main(JsonLoggerTest.java:13)]"}五、类图六、其他在实际的开发过程,我们也可以通过装饰器模式实现不同角色的用户去展示不同的导航菜单
在之前的一次面试中,有被问到,“IO流中用到了什么设计模式?你知道这种设计模式具体怎么实现的吗?”。知道是装饰器模式,但那会没有去了解是怎么具体实现的。下面对这个设计模式做一下简单的总结。一、概述所谓装饰器模式,就是在不改变原有对象的基础之上,去动态的将功能附加到对象之上,提供了比继承更有弹性的替代方法这种设计模式有利也有弊:使功能扩展更加方便创建很多类二、组件装饰器模式(Decorator)中的组件抽象组件(Component) 定义一个抽象类或接口以规范准备接收附加责任的对象下面煎饼果子例子中的BatterCake具体组件(Concrete Component)实现抽象构件,通过装饰角色为其添加一些职责下面煎饼果子例子中的基础煎饼果子BaseBatterCake装饰器(Decorator)继承或实现抽象组件,并包含具体组件的实例,可以通过具体装饰器扩展具体组件的功能下面煎饼果子例子中的BatterCakeDecorator具体装饰器(Cncrete Decorator)实现装饰器的相关方法,并给出具体组件对象添加附加的责任下面煎饼果子例子中的EggDecorator、SausageDecorator三、煎饼果子装饰器实现以我们生活中都吃过的煎饼果子为例我们平常所吃的煎饼果子,组合繁多,有加鸡蛋的、有加香肠的、有加生菜的,等等3.1 抽象组件BetterCake/** * 煎饼果子 */ public interface BetterCake { String getMsg(); int getPrice(); }3.2 具体组件BaseBetterCake/** * 基础的煎饼果子 */ public class BaseBetterCake implements BetterCake { @Override public String getMsg() { return "煎饼"; } @Override public int getPrice() { return 5; } }3.3 装饰器BatterCakeDecorator/** * 装饰器 */ public class BatterCakeDecorator implements BetterCake { private final BetterCake betterCake; public BatterCakeDecorator(BetterCake betterCake) { this.betterCake = betterCake; } @Override public String getMsg() { return this.betterCake.getMsg(); } @Override public int getPrice() { return this.betterCake.getPrice(); } }3.4 具体装饰器具体的装饰器,这里只有加鸡蛋的煎饼果子EggDecorator、加香肠的建斌果子SausageDecorator其他类型的煎饼果子,可以再去继承装饰器3.4.1 EggDecorator/** * 具体装饰器(鸡蛋煎饼果子) */ public class EggDecorator extends BatterCakeDecorator { public EggDecorator(BetterCake betterCake) { super(betterCake); } @Override public String getMsg() { return super.getMsg() + "加1个鸡蛋"; } @Override public int getPrice() { return super.getPrice() + 1; } }3.4.2 SausageDecorator/** * 具体装饰器(香肠煎饼果子) */ public class SausageDecorator extends BatterCakeDecorator { public SausageDecorator(BetterCake betterCake) { super(betterCake); } @Override public String getMsg() { return super.getMsg() + "加1根香肠"; } @Override public int getPrice() { return super.getPrice() + 2; } }3.5 测试类/** * 煎饼果子测试类 */ public class BetterCakeTest { public static void main(String[] args) { BetterCake betterCake; betterCake = new BaseBetterCake(); System.out.println(betterCake.getMsg() + ",售价:" + betterCake.getPrice() + "元"); // 煎饼加鸡蛋 betterCake = new EggDecorator(betterCake); System.out.println(betterCake.getMsg() + ",售价:" + betterCake.getPrice() + "元"); // 煎饼再加鸡蛋 betterCake = new EggDecorator(betterCake); System.out.println(betterCake.getMsg() + ",售价:" + betterCake.getPrice() + "元"); // 煎饼加香肠 betterCake = new SausageDecorator(betterCake); System.out.println(betterCake.getMsg() + ",售价:" + betterCake.getPrice() + "元"); } }3.5.1 执行结果煎饼,售价:5元 煎饼加1个鸡蛋,售价:6元 煎饼加1个鸡蛋加1个鸡蛋,售价:7元 煎饼加1个鸡蛋加1个鸡蛋加1根香肠,售价:9元3.6 类图四、汽车装饰器实现4.1 汽车组件Carpublic interface Car { /** * 汽车的驾驶功能 */ void run(); }4.2 具体组件4.2.1 宝马public class BmwCar implements Car { @Override public void run() { System.out.println("宝马开车了。。。"); } }4.2.2 奔驰public class BenzCar implements Car { @Override public void run() { System.out.println("奔驰开车了。。。"); } }4.2.3 特斯拉public class TeslaCar implements Car { @Override public void run() { System.out.println("特斯拉开车了。。。"); } }4.3 装饰器CarDecoratorpublic class CarDecorator implements Car { protected Car decorated; public CarDecorator(Car decorated) { this.decorated = decorated; } @Override public void run() { decorated.run(); } }4.4 具体装饰器4.4.1 自动驾驶汽车public class AutoCarDecorator extends CarDecorator { public AutoCarDecorator(Car decorated) { super(decorated); } @Override public void run() { super.run(); autoRun(); } private void autoRun() { System.out.println("开启自动驾驶"); } }4.4.2 会飞的汽车public class FlyCarDecorator extends CarDecorator { public FlyCarDecorator(Car decorated) { super(decorated); } @Override public void run() { super.run(); fly(); } private void fly() { System.out.println("开启飞行汽车模式"); } }4.5 测试类public class CarTest { public static void main(String[] args) { Car benzCar = new BenzCar(); Car bmwCar = new BmwCar(); Car teslaCar = new TeslaCar(); // 创建自动驾驶的奔驰汽车 CarDecorator autoBenzCar = new AutoCarDecorator(benzCar); // 创建飞行的、自动驾驶的宝马汽车 CarDecorator flyAutoBmwCar = new FlyCarDecorator(new AutoCarDecorator(bmwCar)); benzCar.run(); bmwCar.run(); teslaCar.run(); System.out.println(); autoBenzCar.run(); System.out.println(); flyAutoBmwCar.run(); } }4.5.1 执行结果奔驰开车了。。。 宝马开车了。。。 特斯拉开车了。。。 奔驰开车了。。。 开启自动驾驶 宝马开车了。。。 开启自动驾驶 开启飞行汽车模式4.6 类图
其实一直对Serverless的理解不太深入,但接触这个Serverless还是比较早。最初是通过云开发平台的活动接触到。部署的应用,用到的FC函数,以及授权的权限等等。后来,在工作的开发中,用到了FC函数去获取号码的归属地,实现客服中心通过号码归属地转接到不同的技能组中。作为个人开发者来说,Serverless无服务还是挺友好的,不必要关心服务器以及服务器的配置,降低了开发成本。将精力都花在代码的开中。对于个人开发者,平常访问的次数比较少,可能会在某一段的时间内有一定的访问量,这时的Serverless的弹性伸缩更能体现出优势。这样我们只需要按照自己的实际使用的资源量去付费即可。 函数应用的创建可以有两种方式1. 通过模板创建应用包含了很多可以快速创建的应用,涵盖了多种语言创建的应用2. 通过仓库导入应用通过仓库导入应用,又支持多种仓库类型的托管来源。这样使得既能来自于公司的代码仓库,又能来自个人的代码仓库等等。通过模板部署一个SpringBoot应用1. 立即创建2. 授权代码仓库通过代码仓库部署暂时支持以下几种GithubGitee自建GitLabCodeup这里授权了Github完成授权后,在仓库用户/组织中显示授权的账号信息3. 应用创建中4. 应用列表及应用信息其中操作栏中包含了访问域名配置删除点击访问域名,会看到下面的页面其实,关于项目的介绍,在代码仓库中都有介绍应用信息应用信息包含了下面几部分基本信息可以访问的域名应用的模板部署的过程创建的时间等等代码源代码源,使用的代码仓库分支初始配置环境变量部署历史其中,在部署历史中可以看到我们过往部署过的历史版本,以及实现版本回退5. 服务及函数在上面创建完应用之后,与此同时会创建一个函数在列表中也将什么是服务进行了介绍在列表中可以看到我们通过此函数访问应用的次数配置应用的访问域名在上面的页面访问中,我们发现访问的域名是一大长串的下面我们将配置成自己的域名,进行访问1. 添加域名2. 云解析DNS控制台添加记录其中,记录值为添加自定义域名中的公网CNAME3. 创建自定义域名4. 访问自定义的域名访问自定义的域名,同样可以看到跟之前一样的结果通过仓库导入应用1. 删除模板部署的应用将志强通过模板部署的SpringBoot应用删除掉,只留下源代码2. 通过仓库导入应用创建应用会看到与模板部署应用一样的界面3. 访问部署域名访问部署域名,同样会看到与之前一样的界面与自己动手去部署的比较如果自己去服务器上部署这么一个SpringBoot应用的话最起码,需要一个java环境。通过Serverless,去点点鼠标,操作操作界面就完成了简单项目的部署后面需要对项目进行修改,完全可以将托管在Github的代码clone到本地,去动手实现自己任何想要实现的效果,push到代码仓库中。在应用的版本控制中,实现新版本的部署。这种的部署方式,对于个人开发者还是很友好的
这里部署的nacos版本是2.1.0一、前置环境有一个能够运行的docker和mysql,可以参考Docker中运行一个mysql二、选择拉取镜像https://hub.docker.com/r/nacos/nacos-server/tags这里选择了2.1.0版本docker pull nacos/nacos-server:v2.1.0三、创建nacos数据库将nacos持久化到mysql数据库中新建nacos数据库从https://github.com/alibaba/nacos/blob/develop/distribution/conf/nacos-mysql.sql下载建表语句。也可以将下列语句粘贴执行/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info */ /******************************************/ CREATE TABLE `config_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(255) DEFAULT NULL, `content` longtext NOT NULL COMMENT 'content', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', `app_name` varchar(128) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `c_desc` varchar(256) DEFAULT NULL, `c_use` varchar(64) DEFAULT NULL, `effect` varchar(64) DEFAULT NULL, `type` varchar(64) DEFAULT NULL, `c_schema` text, `encrypted_data_key` text NOT NULL COMMENT '秘钥', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info_aggr */ /******************************************/ CREATE TABLE `config_info_aggr` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(255) NOT NULL COMMENT 'group_id', `datum_id` varchar(255) NOT NULL COMMENT 'datum_id', `content` longtext NOT NULL COMMENT '内容', `gmt_modified` datetime NOT NULL COMMENT '修改时间', `app_name` varchar(128) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info_beta */ /******************************************/ CREATE TABLE `config_info_beta` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL COMMENT 'content', `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `encrypted_data_key` text NOT NULL COMMENT '秘钥', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info_tag */ /******************************************/ CREATE TABLE `config_info_tag` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', `tag_id` varchar(128) NOT NULL COMMENT 'tag_id', `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL COMMENT 'content', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_tags_relation */ /******************************************/ CREATE TABLE `config_tags_relation` ( `id` bigint(20) NOT NULL COMMENT 'id', `tag_name` varchar(128) NOT NULL COMMENT 'tag_name', `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', `nid` bigint(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`nid`), UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = group_capacity */ /******************************************/ CREATE TABLE `group_capacity` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群', `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_group_id` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = his_config_info */ /******************************************/ CREATE TABLE `his_config_info` ( `id` bigint(64) unsigned NOT NULL, `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `data_id` varchar(255) NOT NULL, `group_id` varchar(128) NOT NULL, `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL, `md5` varchar(32) DEFAULT NULL, `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `src_user` text, `src_ip` varchar(50) DEFAULT NULL, `op_type` char(10) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `encrypted_data_key` text NOT NULL COMMENT '秘钥', PRIMARY KEY (`nid`), KEY `idx_gmt_create` (`gmt_create`), KEY `idx_gmt_modified` (`gmt_modified`), KEY `idx_did` (`data_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = tenant_capacity */ /******************************************/ CREATE TABLE `tenant_capacity` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID', `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表'; CREATE TABLE `tenant_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `kp` varchar(128) NOT NULL COMMENT 'kp', `tenant_id` varchar(128) default '' COMMENT 'tenant_id', `tenant_name` varchar(128) default '' COMMENT 'tenant_name', `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc', `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source', `gmt_create` bigint(20) NOT NULL COMMENT '创建时间', `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info'; CREATE TABLE `users` ( `username` varchar(50) NOT NULL PRIMARY KEY, `password` varchar(500) NOT NULL, `enabled` boolean NOT NULL ); CREATE TABLE `roles` ( `username` varchar(50) NOT NULL, `role` varchar(50) NOT NULL, UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE ); CREATE TABLE `permissions` ( `role` varchar(50) NOT NULL, `resource` varchar(255) NOT NULL, `action` varchar(8) NOT NULL, UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE ); INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');这里的建表语句,与之前版本有点初入,主要是这个字段encrypted_data_key的初入。之前有一个低版本的nacos数据库,配置使用之后导致encrypted_data_key这个字段找不到而报错四、运行容器4.1 先直接部署一个容器是为了拿到application.properties等配置文件docker run -d \ -e MODE=standalone \ -e PREFER_HOST_MODE=hostname \ -e SPRING_DATASOURCE_PLATFORM=mysql \ -e MYSQL_SERVICE_HOST=192.168.120.1 \ -e MYSQL_SERVICE_PORT=3306 \ -e MYSQL_SERVICE_USER=root \ -e MYSQL_SERVICE_PASSWORD=root \ -e MYSQL_SERVICE_DB_NAME=nacos \ -p 8848:8848 \ --name nacos \ --restart=always \ nacos/nacos-server:v2.1.0 参数说明MODE=standalone 单节点模式SPRING_DATASOURCE_PLATFORM=mysql 使用mysql数据库连接方式MYSQL_SERVICE_HOST=192.168.120.1 指定数据库地址MYSQL_SERVICE_PORT 数据库端口MYSQL_SERVICE_USER 数据库用户名MYSQL_SERVICE_PASSWORD 数据库密码MYSQL_SERVICE_DB_NAME 数据库名称-p 8848:8848 端口映射--name nacos 容器命名--restart=always 任意时候重启容器,开机就能自动启动容器(需设置docker为开机自启)Ncaos Docker支持的参数有:具体的可以参见官方文档:https://nacos.io/en-us/docs/quick-start-docker.html4.2 宿主机配置文件映射4.2.1 拷贝配置文件docker cp nacos:/home/nacos/conf/application.properties /data/nacos/config/4.2.2 拷贝logback日志配置文件docker cp nacos:/home/nacos/conf/nacos-logback.xml /data/nacos/config/4.2.3 修改application.properties的配置# spring server.servlet.contextPath=${SERVER_SERVLET_CONTEXTPATH:/nacos} server.contextPath=/nacos server.port=${NACOS_APPLICATION_PORT:8848} # 修改此行,将SPRING_DATASOURCE_PLATFORM的默认值""改为mysql spring.datasource.platform=${SPRING_DATASOURCE_PLATFORM:mysql} nacos.cmdb.dumpTaskInterval=3600 nacos.cmdb.eventTaskInterval=10 nacos.cmdb.labelTaskInterval=300 nacos.cmdb.loadDataAtStart=false db.num=${MYSQL_DATABASE_NUM:1} # 修改此行,添加MYSQL_SERVICE_HOST的默认值为192.168.120.1,MYSQL_SERVICE_DB_NAME的默认值为nacos db.url.0=jdbc:mysql://${MYSQL_SERVICE_HOST:192.168.120.1}:${MYSQL_SERVICE_PORT:3306}/${MYSQL_SERVICE_DB_NAME:nacos}?${MYSQL_SERVICE_DB_PARAM:characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false} # 修改此行,添加MYSQL_SERVICE_HOST的默认值为192.168.120.1,MYSQL_SERVICE_DB_NAME的默认值为nacos db.url.1=jdbc:mysql://${MYSQL_SERVICE_HOST:192.168.120.1}:${MYSQL_SERVICE_PORT:3306}/${MYSQL_SERVICE_DB_NAME:nacos}?${MYSQL_SERVICE_DB_PARAM:characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false} # 修改此行,添加MYSQL_SERVICE_USER的默认值为root db.user=${MYSQL_SERVICE_USER:root} # 修改此行,添加MYSQL_SERVICE_PASSWORD的默认值为root db.password=${MYSQL_SERVICE_PASSWORD:root} ### The auth system to use, currently only 'nacos' is supported: nacos.core.auth.system.type=${NACOS_AUTH_SYSTEM_TYPE:nacos} ### The token expiration in seconds: nacos.core.auth.default.token.expire.seconds=${NACOS_AUTH_TOKEN_EXPIRE_SECONDS:18000} ### The default token: nacos.core.auth.default.token.secret.key=${NACOS_AUTH_TOKEN:SecretKey012345678901234567890123456789012345678901234567890123456789} ### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. nacos.core.auth.caching.enabled=${NACOS_AUTH_CACHE_ENABLE:false} nacos.core.auth.enable.userAgentAuthWhite=${NACOS_AUTH_USER_AGENT_AUTH_WHITE_ENABLE:false} nacos.core.auth.server.identity.key=${NACOS_AUTH_IDENTITY_KEY:serverIdentity} nacos.core.auth.server.identity.value=${NACOS_AUTH_IDENTITY_VALUE:security} server.tomcat.accesslog.enabled=${TOMCAT_ACCESSLOG_ENABLED:false} server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D # default current work dir server.tomcat.basedir= ## spring security config ### turn off security nacos.security.ignore.urls=${NACOS_SECURITY_IGNORE_URLS:/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**}采用添加默认值的方式,这样不会影响指定命令行的参数4.3 运行容器运行之前先删除之前启动的容器docker stop nacos docker rm nacos重新运行容器docker run -d \ -e MODE=standalone \ -p 8848:8848 \ -v /data/nacos/conf:/home/nacos/conf \ -v /data/nacos/logs:/home/nacos/logs \ -v /data/nacos/data:/home/nacos/data \ --name nacos-mysql \ --restart=always \ nacos/nacos-server:v2.1.0访问:ip:8848/nacos
一、集成Swagger21. pom引用<properties> <java.version>1.8</java.version> <swagger.version>2.9.2</swagger.version> <fastjson.version>1.2.75</fastjson.version> <hutool.version>5.7.16</hutool.version> </properties> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>说明:项目中使用了Validated,即hibernate-validatorswagger使用了2.9.2版本springfox-swagger2springfox-swagger-uifastjson不再赘述hutool-all java工具类库https://www.hutool.cn/lombok在编译期生成get、set、toString等等,减少构建代码2. Swagger2配置类package com.example.config; import org.springframework.context.annotation.Configuration; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { }3. 访问Swagger2页面访问地址:http://localhost:9090/swagger-ui.html4. 在集成的过程中出现的问题一开始使用的SpringBoot版本是2.7.0在项目启动时,出现错误在之前集成的时候,使用的SpringBoot版本是2.5.6,就没有遇到上面的错误后来去网上百度时,出现这个问题的原因是SpringBoot 2.6.0中将SpringMVC的默认路径匹配策略从AntPathMatcher更改为PathPatternParser,导致出错。解决办法是:切回原先的AntPathMatcher采用降低SpringBoot的版本或者更改路径匹配策略,都可吧。后者对于以后的使用上未知。除去以上两种方式,还有未验证的一种方式添加对guava的依赖<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.1-jre</version> </dependency>二、集成Swagger31. pom引用<properties> <java.version>1.8</java.version> <springfox.version>3.0.0</springfox.version> <fastjson.version>1.2.75</fastjson.version> <hutool.version>5.7.16</hutool.version> </properties> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>${springfox.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool.version}</version> </dependency>与上面Swagger2不同之处在于,由两个引用替换成了一个springfox-boot-starter引用2. Swagger3配置类在Swagger3中如果使用默认的配置,是不需要Configuration的配置类在Springfox的介绍中,移除了@EnableSwagger2 注解不需要配置类,直接启动项目即可3. 访问Swagger3页面访问地址:http://localhost:8080/swagger-ui/在界面上与Swagger2稍微有点不同4. 在集成的过程中出现的问题同上,也出现过类加载的问题可以使用前两种方案解决三、Swagger使用关于使用,不再区分swagger2或者swagger3。估计使用上大面上是一致的1. 配置文档信息1.1 在swagger2中package com.example.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.concurrent.CopyOnWriteArrayList; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()); } private ApiInfo apiInfo() { Contact contact = new Contact("阿里云", "https://www.aliyun.com/", ""); return new ApiInfo( //标题 "文档标题", //描述 "文档描述", //版本 "文档版本1.0.0", //组织连接 "https://www.aliyun.com/", //联系人信息 contact, //许可证 "Apache 2.0 许可", //许可连接 "http://www.apache.org/licenses/LICENSE-2.0", //扩展 new CopyOnWriteArrayList<>()); } }1.2 在swagger3中package com.example.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import java.util.concurrent.CopyOnWriteArrayList; @Configuration public class SwaggerConfig { @Bean public Docket docket() { return new Docket(DocumentationType.OAS_30).apiInfo(apiInfo()); } private ApiInfo apiInfo() { Contact contact = new Contact("阿里云", "https://www.aliyun.com/", ""); return new ApiInfo( //标题 "文档标题", //描述 "文档描述", //版本 "文档版本1.0.0", //组织连接 "https://www.aliyun.com/", //联系人信息 contact, //许可证 "Apache 2.0 许可", //许可连接 "http://www.apache.org/licenses/LICENSE-2.0", //扩展 new CopyOnWriteArrayList<>()); } }1.3 页面展示2. 配置接口扫描@Bean public Docket docket() { // DocumentationType版本的区别 return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.example")) .build(); }在select()和build()之间加入才生效apis()的参数any() 配置扫描所有none() 不扫描接口withMethodAnnotation() 配置扫描指定的方法注解withClassAnnotation() 配置扫描指定的类注解basePackage() 配置扫描指定的包路径3. 配置接口扫描的path@Bean public Docket docket() { // DocumentationType版本的区别 return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.example")) .paths(PathSelectors.any()) .build(); } paths()的参数any() 任何请求都扫描none() 任何请求都不扫描regex() 符合正则表达式的ant() 符合ant的路径4. 配置Api的分组在没有配置分组时,默认是default。可以通过groupName()方法设置分组@Bean public Docket docket() { return docketGroup(""); } @Bean public Docket docket1() { return docketGroup("group1"); } @Bean public Docket docket2() { return docketGroup("group2"); } private Docket docketGroup(String groupName) { Docket docket = new Docket(DocumentationType.OAS_30); if (StrUtil.isNotBlank(groupName)) { docket.groupName(groupName); } docket.apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.example")) .paths(PathSelectors.any()) .build(); return docket; }指定三个分组,分别:default、group1、group2。5. 配置是否开启Swagger可以通过enable()方法来配置是否开启swagger/** * Hook to externally control auto initialization of this swagger plugin instance. * Typically used if defer initialization. * * @param externallyConfiguredFlag - true to turn it on, false to turn it off * @return this Docket */ public Docket enable(boolean externallyConfiguredFlag) { this.enabled = externallyConfiguredFlag; return this; }配置default、dev、test环境开启Swagger@Bean public Docket docket(Environment env) { return docketGroup("") .enable(env.acceptsProfiles(Profiles.of("default", "dev", "test"))); }配置的参数即spring.profiles.active指定的值,不指定,默认值为default6. Controller接口创建package com.example.controller; import com.example.entity.Student; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @Slf4j @Valid @Validated @RestController @AllArgsConstructor @RequestMapping("/api") @Api(value = "3.0版本api请求", tags = "3.0版本api请求") public class ApiController { @GetMapping("/test") @ApiOperation(value = "3.0测试接口", notes = "该接口的描述") public String test() { return "test"; } @GetMapping("/student") @ApiOperation(value = "学生类接口", notes = "获取一个学生对象") public Student student() { Student student = new Student(); student.setName("张三"); student.setAge(18); return student; } }Student学生实体类package com.example.entity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @Data @ApiModel(value = "学生类实体") public class Student { @ApiModelProperty("姓名") private String name; @ApiModelProperty("年龄") private Integer age; }7. Swagger的不同ui风格7.1 默认ui访问地址:http://localhost:8080/swagger-ui/<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>${springfox.version}</version> </dependency>7.2 bootstrap ui访问地址:http://localhost:8080/doc.html<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency>后来被knife4j代替7.3 Layui ui访问地址:http://localhost:8080/docs.html<dependency> <groupId>com.github.caspar-chen</groupId> <artifactId>swagger-ui-layer</artifactId> <version>1.1.3</version> </dependency>这个基于Layui的好像不能切换分组,没找到切换分组的地方.更不幸的是,这个项目已经停止维护了7.4 knife4jhttps://doc.xiaominfo.com/knife4j/documentation/集成的选择比较多,可以自行选择。此外,衍生出来的功能也比较多,可以尝试去探索。更可以参考网站文档去使用:https://doc.xiaominfo.com/knife4j/documentation/四、关于Swagger2和Swagger3https://github.com/springfox/springfox
一、拉取镜像docker pull elasticsearch:7.14.0二、运行容器docker run -d --name es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.14.02.1 设置内存docker run -d --name es2 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m" elasticsearch:7.14.02.2 配置允许跨域# 进入es容器内部 docker exec -it es bash # 修改es的配置文件 vi /usr/share/elasticsearch/config/elasticsearch.yml # 添加配置 http.cors.enabled: true http.cors.allow-origin: "*"访问es:localhost:9200三、安装ik分词器3.1 下载ik分词器wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.14.0/elasticsearch-analysis-ik-7.14.0.zip这里一定保证分词器的版本和es的版本一致,否则不能正常启动选择合适的版本:https://github.com/medcl/elasticsearch-analysis-ik/releases这里如果使用wget比较慢的话,可以先下载下来,通过xftp传上去3.2 解压文件# 解压到ik目录下 unzip elasticsearch-analysis-ik-7.14.0.zip -d ./ik/3.3 配置# 将ik文件夹cp到es容器中 docker cp ./ik/ es:/usr/share/elasticsearch/plugins/ # 重启es docker restart es # 校验es curl localhost:9200四、安装kibana4.1 获取镜像docker pull kibana:7.14.04.2 运行容器docker run -d --name kibana -p 5601:5601 kibana:7.14.04.4 配置docker exec -it kibana bash vi /usr/share/kibana/conf/kibana.yml # 在最后添加 i18n.locale: "zh-CN" # 同时修改kibana连接es的ip # 修改这一行elasticsearch.hosts: [ "http://172.17.0.2:9200" ] elasticsearch.hosts: [ "http://172.17.0.2:9200" ]其中:172.17.0.2可以通过docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [container_name/container_id]五、最后还没有完成的是,配置、数据的持久化。将配置、数据等存在宿主机中,不至于容器删除数据丢失等等
一、依赖引用<dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId> <version>5.5.9</version> </dependency> <dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId> <version>1.2.5</version> </dependency>二、配置类包含接收消息的配置和发送消息的配置package com.demo.config; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.core.MessageProducer; import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; import org.springframework.integration.mqtt.core.MqttPahoClientFactory; import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import java.util.UUID; /** * mqtt连接配置 */ @Configuration public class MqttConfig { /** * 创建连接 * * @return */ @Bean public MqttPahoClientFactory mqttClientFactory() { DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); MqttConnectOptions options = new MqttConnectOptions(); // mqtt用户名&密码 String userName = ""; String pwd = ""; // mqtt服务地址,可以是多个 options.setServerURIs(new String[]{"tcp://server:1883"}); options.setUserName(userName); options.setPassword(pwd.toCharArray()); factory.setConnectionOptions(options); return factory; } /** * 2、接收消息的通道 */ @Bean public MessageChannel mqttInputChannel() { return new DirectChannel(); } /** * 接收消息 * * @return */ @Bean public MessageProducer inbound() { // 订阅主题,保证唯一性 String inClientId = UUID.randomUUID().toString().replaceAll("-", ""); // 最后的#相当于通配符的概念 String[] topic = {"topic_prefix/topic/#"}; MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter( inClientId, mqttClientFactory(), topic); adapter.setCompletionTimeout(5000); DefaultPahoMessageConverter defaultPahoMessageConverter = new DefaultPahoMessageConverter(); // 按字节接收消息 // defaultPahoMessageConverter.setPayloadAsBytes(true); adapter.setConverter(defaultPahoMessageConverter); // 设置QoS adapter.setQos(1); adapter.setOutputChannel(mqttInputChannel()); return adapter; } /** * 3、消息处理 * ServiceActivator注解表明:当前方法用于处理MQTT消息,inputChannel参数指定了用于消费消息的channel */ @Bean @ServiceActivator(inputChannel = "mqttInputChannel") public MessageHandler handler() { return message -> { String payload = message.getPayload().toString(); // byte[] bytes = (byte[]) message.getPayload(); // 收到的消息是字节格式 String topic = message.getHeaders().get("mqtt_receivedTopic").toString(); // 可以根据topic进行处理不同的业务类型 System.out.println("主题[" + topic + "],负载:" + payload); }; } /** * 发送消息的通道 * * @return */ @Bean public MessageChannel mqttOutboundChannel() { return new DirectChannel(); } /** * 发送消息 */ @Bean @ServiceActivator(inputChannel = "mqttOutboundChannel") public MessageHandler outbound() { // 连接clientId保证唯一 String outClientId = UUID.randomUUID().toString().replaceAll("-", ""); // 发送消息和消费消息Channel可以使用相同MqttPahoClientFactory MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(outClientId, mqttClientFactory()); // 如果设置成true,即异步,发送消息时将不会阻塞。 // messageHandler.setAsync(true); // 设置默认的topic // messageHandler.setDefaultTopic("defaultTopic"); // 设置默认QoS messageHandler.setDefaultQos(1); // Paho消息转换器 DefaultPahoMessageConverter defaultPahoMessageConverter = new DefaultPahoMessageConverter(); // 发送默认按字节类型发送消息 // defaultPahoMessageConverter.setPayloadAsBytes(true); messageHandler.setConverter(defaultPahoMessageConverter); return messageHandler; } }三、消息发送1. 定义消息发送的接口package com.demo.config; import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.mqtt.support.MqttHeaders; import org.springframework.messaging.handler.annotation.Header; /** * 定义消息发送的接口 */ @MessagingGateway(defaultRequestChannel = "mqttOutboundChannel") public interface MqttGateWay { /** * 发送消息 * * @param payload 发送的消息 */ void sendToMqtt(String payload); /** * 指定topic消息发送 * * @param topic 指定topic * @param payload 消息 */ void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload); void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload); void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, byte[] payload); }2. 定义消息发送的controllerpackage com.demo.business; import com.sonli.config.MqttGateWay; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 对外暴露发送消息的controller */ @RestController @RequestMapping("/mqtt") public class MqttController { @Autowired private MqttGateWay mqttGateWay; @PostMapping("/sendMessage") public String sendMessage(String topic, String message) { // 发送消息到指定topic mqttGateWay.sendToMqtt(topic, 1, message); return "send topic: " + topic + ", message : " + message; } }3. 测试自己发送,自己监听3.1 发送消息3.2 消息的监听,收到的消息
在上一篇中我们已经创建了一个语音技能,实现了“欢迎”-> “Hello World”这一篇中,我们要创建一个查询天气的意图,实现“语音精灵”-> “我在,你说,我可以帮你查询天气”一、 配置语音交互模型1.1 新建一个“意图”编辑已经创建好的语音技能,选择“语音交互模块”,点击“创建意图”,创建一个“天气查询意图”填写“意图名称”和“意图标识”1.2 配置单轮对话的语料语料:是指当用户为了达到目的向音箱说出的语音指令。单轮对话语料的含义是:当用户说这句话时,就可以确认用户就是希望使用这个意图的功能。语料的泛化:用户不可能只按照一句语料来说,所以相似的表达也应该配置成语料,这叫做语料的泛化。如:“杭州今天天气怎么样”,“杭州明天天气怎么样”,“杭州今天天气如何”,“北京天气怎么样”配置“单轮对话表达”,输入表达语句,回车添加,添加完成后,提交1.3 创建配置实体我们在进行配置意图语料时,天气查询涉及到杭州(城市)、今天(时间)这两个参数,而我们在配置中又不可能将所有城市和时间穷举。这时,通过实体就可以帮助我们很好的解决这两个参数的配置。创建一个自定义实体,填写实体名称和实体标识名,并添加实体值,如:北京、杭州、上海等,中间以空格分隔,回车保存创建一个时间 公共实体(sys.date),点击引用公共实体,搜索sys.date回车,打开引用按钮以下就是我们所配置的实体列表,一个城市自定义实体,一个日期公共实体1.4 进行实体标注编辑意图中的天气查询,选中“今天”,在弹框中选择我们实体中的sys.date。同理,选中“杭州”,在弹框中选择实体city1.5 参数默认及参数追问有时,我们会这么问“杭州天气怎么样”,相当于默认日期为“今天”,此时我们可以这样配置有时,我们也会这么问“今天天气怎么样”,我们就不知道要查询的是哪个城市的天气,此时,我们就可以配置追问,在城市参数中点击编辑,添加追问语句,“您要查询哪个城市的天气”1.6 配置多轮对话有时,在问完,“杭州天气怎么样”之后,会接着问“那明天呢”,“那北京呢”等等,添加多轮对话编辑,并进行实体标注二、 开发部署后端服务2.1 在后端服务模块,点击左侧服务部署,点击前往开发按钮前往CloudIDE平台开发技能2.2 在CloudIDE平台进行代码修改package com.alibaba.ailabs; import com.alibaba.ailabs.common.AbstractEntry; import com.alibaba.da.coin.ide.spi.meta.AskedInfoMsg; import com.alibaba.da.coin.ide.spi.meta.ExecuteCode; import com.alibaba.da.coin.ide.spi.meta.ResultType; import com.alibaba.da.coin.ide.spi.standard.ResultModel; import com.alibaba.da.coin.ide.spi.standard.TaskQuery; import com.alibaba.da.coin.ide.spi.standard.TaskResult; import com.alibaba.fastjson.JSON; import com.aliyun.fc.runtime.Context; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * @Description 天猫精灵技能函数入口,FC * handler:com.alibaba.ailabs.GenieEntry::handleRequest * @Version 1.0 **/ public class GenieEntry extends AbstractEntry { @Override public ResultModel<TaskResult> execute(TaskQuery taskQuery, Context context) { context.getLogger().info("taskQuery: " + JSON.toJSONString(taskQuery)); // ResultModel<TaskResult> res = new ResultModel<>(); TaskResult taskResult = new TaskResult(); // 从请求中获取意图参数以及参数值 Map<String, String> paramMap = taskQuery.getSlotEntities().stream().collect(Collectors.toMap(slotItem -> slotItem.getIntentParameterName(), slotItem -> slotItem.getOriginalValue())); //处理名称为 welcome 的意图 if ("welcome".equals(taskQuery.getIntentName())) { taskResult.setReply("我在,你说,我可以帮你查询天气"); //处理名称为 weather 的意图 } else if ("weather".equals(taskQuery.getIntentName())) { //weather 意图中 date 参数勾选了必选,请求数据中一定会携带 date 参数,只需要判断 city 参数有没有。 if (paramMap.get("city") == null) { taskResult.setReply("您要查询哪个城市的天气?"); return askReply(taskResult, "city", taskQuery.getIntentId()); } //TODO 根据参数获取天气信息,这里使用假数据替代 taskResult.setReply(paramMap.get("city") + paramMap.get("sys.date(公共实体)") + "天气 晴"); //处理名称为 ari_quality 的意图 }else { taskResult.setReply("请检查意图名称是否正确,或者新增的意图没有在代码里添加对应的处理分支。"); } return reply(taskResult); } /** * 结束对话的回复,回复后音箱闭麦 */ private ResultModel<TaskResult> reply(TaskResult taskResult) { ResultModel<TaskResult> res = new ResultModel<>(); taskResult.setExecuteCode(ExecuteCode.SUCCESS); taskResult.setResultType(ResultType.RESULT); res.setReturnCode("0"); res.setReturnValue(taskResult); return res; } /** * 指定追问参数,音箱自动开麦,用户的回答优先匹配追问的参数 */ private ResultModel<TaskResult> askReply(TaskResult taskResult, String parameterName, Long intentId) { ResultModel<TaskResult> res = new ResultModel<>(); taskResult.setExecuteCode(ExecuteCode.SUCCESS); taskResult.setResultType(ResultType.ASK_INF); AskedInfoMsg askedInfoMsg = new AskedInfoMsg(); askedInfoMsg.setIntentId(intentId); askedInfoMsg.setParameterName(parameterName); List<AskedInfoMsg> askedInfos = new ArrayList<>(); askedInfos.add(askedInfoMsg); taskResult.setAskedInfos(askedInfos); res.setReturnValue(taskResult); return res; } }2.3 完成代码修改后,提交到代码仓库保存,并部署“预发环境”三、语音技能测试3.1 进入在线测试进行测试3.2 测试结果四、应用下线在线测试完成后,要在云开发平台中我的应用及时将部署的应用下线,因为免费额度有限,不及时下线将会产生不必要的费用下一篇,我们将实现意图的切换,参数的传递天猫技能应用平台官方文档:https://aligenie.com/doc/20255408/pvtgu9文章搬自:https://blog.csdn.net/miao_x_m/article/details/121135791
涉及到的平台天猫精灵开放平台阿里云 云开发平台创建第一个[Hello World]技能一、天猫精灵开放平台登录1.1 登录之后,在右上角的控制台中选择技能应用平台1.2 首次登录时,您需要签署平台合作协议该认证的认证,该确定的确定,都是这么过来的~~~二、创建一个“语音技能”2.1 在“我的技能应用”Tab中,选择“语音技能”->“创建新技能”2.2 填写节能基本信息选中 语音技能 中的 自定义技能 填写技能创建信息:技能名称 和 调用词技能名称:展示在技能市场的技能名称,在发布时将进行唯一性检测。技能属性:这里选择公有技能。私有技能是针对的是定制机和生态设备技能调用词:也称为 唤醒词,是用户使用这个技能所需要说的关键字。如:在调试阶段输入“欢迎”,回复“Hello World”后端资源的部署方式:选择阿里云云开发(FAAS)点击确认创建2.3 技能创建成功三、配置语音交互模型3.1 点击创建意图3.2 设置意图名称、意图标识,并将这条意图设置为默认意图,设置好点击提交四、后端服务4.1 顶部导航栏中选择后端服务,并在左侧导航栏中选择服务部署4.2 点击继续部署,选择阿里云云原生(FAAS)4.3 关联阿里云账号4.4 选择阿里云账号的登录方式4.5 登录阿里云账号(如果没有阿里云账号就注册一个)4.6 登录成功后需要签署《云开发平台服务协议》,签署之后就可以免费使用云开发平台进行服务开发了4.7 签署协议后,在团队注册页翻到页面底部,点击“统一授权”,授权后点击“下一步”4.8 完成并返回 技能开发平台4.9 开通开运资源4.10 创建技能应用,点击保存4.11 点击“前往开发”,跳转到“阿里云开发平台CloudIDE”4.12 发布“预发环境”,去技能平台进行在线测试五、在线测试在输入唤醒词中,输入“欢迎”,返回“Hello World”六、真机测试如有“真机”,也可以“真机测试”。七、应用下线在测试完成后,要在云开发平台中“我的应用”及时将部署的应用下线,因为免费额度有限,不及时下线将会产生不必要的费用一定要注意!!!写在最后:开发文档详见https://aligenie.com/doc/20255408/yk3i51下一篇,我们将实现与“语音技能”的简单对话文章搬自:https://blog.csdn.net/miao_x_m/article/details/121105307
一、数据准备1. Student对象@Data是lombokimport lombok.Data; @Data public class Student { /** 姓名 */ private String name; /** 年龄 */ private Integer age; /** 成绩 */ private Double score; /** 是否优秀学生 */ private Boolean merit; }2. 随机生成学生数据2.1 生成年龄(16~19岁)private static int getRandomAge() { Random random = new Random(); return random.nextInt(3) + 16; }2.2 生成成绩(0~100分)两位小数private static double getRandomScore() { Random random = new Random(); double a = random.nextDouble() * 100; DecimalFormat df = new DecimalFormat("0.00"); String str = df.format(a); return Double.parseDouble(str); }2.3 生成优秀学生private static boolean getRandomMerit() { Random random = new Random(); return random.nextBoolean(); }2.4 生成50名学生的数据private static List<Student> getStudentList() { List<Student> list = new ArrayList<>(); String name = "张"; for (int i = 1; i <= 5; i++) { Student student = new Student(); // 姓名拼接 student.setName(name + i); student.setAge(getRandomAge()); // 设置缺考成绩 if (i == 2 || i == 3) { student.setScore(null); } else { student.setScore(getRandomScore()); } student.setMerit(getRandomMerit()); list.add(student); } return list; }二、数据排序1. 简单用法假设没有缺考的学生,即学生都有成绩,都是随机生成的成绩不走if(i == 2 || i == 3)的判断1.1 排序年龄List<Student> studentList = getStudentList(); // 进行年龄排序-正序 studentList.sort(Comparator.comparingInt(Student::getAge)); // 排序-倒序 studentList.sort(Comparator.comparingInt(Student::getAge).reversed());1.2 排序成绩List<Student> studentList = getStudentList(); // 进行成绩排序-正序 studentList.sort(Comparator.comparingDouble(Student::getScore)); // 排序-倒序 studentList.sort(Comparator.comparingDouble(Student::getScore).reversed());1.3 以此类推# 排序优秀学生,使用了Boolean中的compare,而非比较器 studentList.sort((o1, o2) -> Boolean.compare(o1.getMerit(), o2.getMerit())); # 排序学生名称-倒序,使用了通用的比较Comparator.comparing studentList.sort(Comparator.comparing(Student::getName).reversed());2. 组合用法2.1 按年龄正序排序,年龄相等的,按成绩倒序排studentList.sort(Comparator.comparingInt(Student::getAge).thenComparing(Comparator.comparingDouble(Student::getScore).reversed())); # 注意,下面这种跟上面是不同的结果,注意 studentList.sort(Comparator.comparingInt(Student::getAge).thenComparingDouble(Student::getScore).reversed());2.2 将没有成绩的排到最后或者最前// 排到最后 studentList.sort(Comparator.comparing(Student::getScore, Comparator.nullsLast(Double::compareTo))); // 排到最前 studentList.sort(Comparator.comparing(Student::getScore, Comparator.nullsFirst(Double::compareTo)));2.3 将空对象排到最后或者最前所谓空对象,即student对象为nullstudentList.add(null);// 排到最后 studentList.sort(Comparator.nullsLast(Comparator.comparing(Student::getScore, Comparator.nullsFirst(Double::compareTo)))); // 排到最前 studentList.sort(Comparator.nullsFirst(Comparator.comparing(Student::getScore, Comparator.nullsFirst(Double::compareTo))));3. Comparator比较器分类3.1 reversed()翻转比较结果,正序变倒序3.2 thenComparing开头的进行二次比较使用,即多个比较条件的3.3 nullsFirst和nullsLastnull值的对象或者null值的属性放在最前或者最后注意区分null对象和null值属性的不同用法3.4 comparing开头正常使用的比较器
1. 新建一个文件随便找一个地方(为了方便,一般都在桌面)新建一个文本文档,即新建文本文档.txt2. 编辑新建的文件在里面输入DEL /F /A /Q \\?\%1 RD /S /Q \\?\%1不要问,这是什么命令,我也是去百度的,验证之后是好用的3. 重命名文件将文件的扩展名.txt改为.bat,即将新建文本文档.txt改为新建文本文档.bat文件的名称是什么不重要,重要的是文件的扩展名一定是.bat,一定是.bat,一定是.bat重要的事情说三遍!!!4. 使用把需要删除的文件(夹),就是提示“该项目不在XX中,请确认该项目的位置然后重试”的这个文件(夹)拖到这个.bat文件上,就可以解决这个删除不掉的问题了5. 其他防止后面还会出现这种问题,就可以将这个.bat文件放到某个角落里了,下次再出现这种问题,就可以用这个角落里的文件去解决这个烦恼的问题了
一、准备1. gccnginx 需要先将官网下载的源码进行编译,编译依赖 gcc 环境yum -y install gcc2. pcre、pcre-develpcre是一个perl库,包括perl兼容的正则表达式库,nginx的http模块使用pcre来解析正则表达式,所以需要安装pcre库。总结来说,就是pcre是来正则表达式的yum install -y pcre pcre-devel3. zlibzlib库用于对HTTP包的内容做gzip格式的压缩,如果我们在nginx.conf中配置了gzip on,并指定对于某些类型(content-type)的HTTP响应使用gzip来进行压缩以减少网络传输量,则在编译时就必须把zlib编译进Nginx。zlib-devel是二次开发所需要的库。yum install -y zlib zlib-devel4. openssl如果服务器不只是要支持HTTP,还需要在更安全的SSL协议上传输HTTP,那么需要拥有OpenSSL。另外,如果我们想使用MD5、SHA1等散列函数,那么也需要安装它。yum install -y openssl openssl-devel二、安装1. 下载安装包这里直接使用了stable version,http://nginx.org/en/download.html使用的是1.20.2版本wget http://nginx.org/download/nginx-1.20.2.tar.gz其他版本的下载,https://nginx.org/download/2. 解压解压到当前目录tar -zxvf nginx-1.20.2.tar.gz3. 进行make切换到解压出来的文件夹目录中,如cd nginx-1.20.2 pwd /root/nginx-1.20.2执行下面三个命令./configure make make install注意:第一个./configure命令,如果是简单的安装,让nginx跑起来的话,直接./configure 是没有问题的。在后面配置https的时候是有坑的后面的两个命令就是编译安装了 4. 启动切换到nginx的默认安装目录下的sbin目录下cd /usr/local/nginx/sbin # 启动程序 ./nginx使用ps查看启动的进程号ps -ef|grep nginx三、配置https1. ssl证书准备阿里云的ssl证书,一年可以有20张的免费额度。ssl证书控制台2. 证书申请,绑定域名选择证书申请,根据提示,一步步是申请接入即可3. 下载证书在状态这一列中,点击那个感叹号,会有一个下载按钮,会展示需要安装到的web服务器类型这里选择的是nginx类型。旁边提示的帮助,就是帮助我们一步步的完成https的配置4. 编辑配置文件nginx.conf阿里云官方帮助文档https://help.aliyun.com/document_detail/98728.html5. 重新启动在按照文章配置完成后,进行了重新启动。就有了下面的问题,[emerg] the "ssl" parameter requires ngx_http_ssl_module in /usr/local/nginxssl需要ngx_http_ssl_module 模块查看是否开启ssl模块/usr/local/nginx/sbin/nginx -V正常开启之后在configure argument之后是有参数的,即下面红框中的这也与前面./configure对应起来了,我们的确在后面没有加任何的参数下面我们就要弥补这个问题了找到从压缩文件加压出来的文件执行命令./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module make # 注意不要执行make install了,这样会覆盖掉原先的安装的备份原先的的nginx启动文件cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak将刚刚编译好的nginx覆盖掉原先的nginx,这个时候nginx需要停止/usr/local/nginx/sbin/nginx -s stop # 停止不了的话 ps -ef|grep nginx kill -9 [pid] cp ./objs/nginx /usr/local/nginx/sbin/启动nginx/usr/local/nginx/sbin/nginx查看安装/usr/local/nginx/sbin/nginx -V
前言天猫精灵的技能应用平台提供了从语音交互模型定义、语义解析理解能力,到技能开发、测试、部署的一整套开发工具和便捷的可视化操作工作台,帮助开发者高效地将各类技能应用快速接入到天猫精灵音箱以及精灵生态硬件终端。这里我们将不写一行代码,通过鼠标点点,实现一个电影推荐的小技能~~~实现过程1. 创建语音技能在技能应用平台中,选择语音技能,点击创建新技能填写技能的基本信息选中 语音技能 中的 自定义技能 填写技能创建信息:技能名称和调用词技能名称:展示在技能市场的技能名称,在发布时将进行唯一性检测。技能属性:可选择 公有技能 / 私有技能。技能属性选择,建议您先参考【公有&私有】,由于我们要开发一个面向所有天猫精灵用户的应用,所以技能属性这里选择公有技能。技能调用词:也称为 唤醒词,是用户使用这个技能所需要说的关键字。如“天猫精灵,电影查询助手”,则使用了调用词为“电影查询助手”的技能。基本信息填好后,后端服务选择阿里云云开发(FAAS)模式,点击确认创建2. 开发部署后端服务技能创建成功后,进入技能基本信息页面。点击进入后端服务。顶部导航栏中选择后端服务,并在左侧导航栏中选择服务部署。点击编辑部署后,选择阿里云云原生开发(FAAS),点击关联阿里云账号授权完成后,会是下面的样子确认4个服务资源均已开通后,点击创建技能应用。选择NodeJS、高分电影推荐。最终创建好的服务,是下面的样子后端技能应用创建完成后,点击前往开发,跳转至云开发平台云开发平台会自动进入 CloudIDE,平台会自动生成模板代码。由于使用了模板,所以无须再开发代码,进入 CloudIDE后直接选择部署环境,打开 CloudIDE 左侧的 部署调试插件,进入到部署面板,选择预发环境进行部署。部署完成后,会有相应的日志信息展示后面也可以选择线上环境进行部署,部署完成后同样会出现部署成功的提示。3. 语音技能测试后端服务创建后,在语音交互模型中,意图和实体已经自动创建好了在线测试。进入测试模块,打开在线测试也可以进行真机测试至此,我们就完成了,语音技能的创建与测试。4. 发布与审核技能可以将自己创建的技能进行发布,填写完相应信息后,进行审核。最后生活中从不缺少想法,可以将生活中的好的idea通过代码实现,给生活添加点儿小乐趣。
前言:说到服务器ECS,使用的第一台ECS是n4型的,在2019年7月29日,作为新用户购买还是很划算的,此处仅仅强调新用户。在日常的开发过程中,总是需要各种各样的环境。像日常开发软件环境的部署了、新开发的系统需要部署到服务中了,都需要一台云服务器。这里对比一下本地的虚拟机,相比较虚拟机,虚拟机是零成本的,只要自己的本地计算机的硬件足够支持,可以创建n个本地虚拟机。但是虚拟机随着本地计算机的关机而关机,而云服务器,随时都可以拿来用,甚至能够做到域名的映射,方便开发。一、ECS是什么阿里云给出的概念是云服务器ECS(Elastic Compute Service)是一种弹性可伸缩的云计算服务,助您降低IT成本,提高运维效率,使您更专注于核心业务创新。其实,就相当于是一台虚拟机,只不过是云上的。所谓云上的,就是由阿里云云服务进行集成,我们只需要使用即可。平时的练手、部署一个小型网站等等,一台1核2G的服务器足够玩的欢乐了二、ECS控制台2.1 概览在概览中陈列了自己的云服务器资源,同时还提供了一些教程快速搭建网站部署LAMP开发环境搭建WordPress云上博客搭建微信、支付宝小程序云上服务器的迁移云上高可用架构对于这些教程,对于大家还是很友好的,有详尽的文档手册,在开发者社区中还提供了许多的场景,以供我们选择。第一次在阿里云的产品看到了腾讯产品(微信)的字眼~~~2.2 实例实例中描述了我们的云服务器的属性以及相对应的操作选项。属性中,像实例ID、安装的系统、归属的可用区、公网和私网下的IP地址、配置以及购买服务器的方式操作选项中,远程连接(一般都使用xshell连接)、更换操作系统、重置密码、重启服务器等等,总能找到你想要的操作。2.3 安全组就像自己本地的防火墙,只不过交由了阿里云进行管理。如果自己部署了某个服务,使用了某个端口,一定要在安全组的访问规则中进行放行,同时还能设置授权对象,指定某一个ip2.4 快照与云盘与自己本地的虚拟机不香的是,给云服务器创建快照,将产生资源费用。如果能给提供一定少量的免费额度就好了云盘,就像自己电脑上的硬盘,来进行存储数据三、日常使用以前都是直接在ECS上安装Mysql、Nacos、Jdk、Tomcat、Redis、Mongo、Gitlab、Jenkins等等开发环境。一直在系统重置与安装软件中折磨这个服务器。随机Docker镜像服务使用,使得现在部署上面的开发服务镜像变得越来越简单了。虽说不建议在Docker中部署运行Mysql,但个人使用、部署确实方便。近期就在ECS中安装了Docker,运行了Mysql的镜像,并将Mysql的data数据挂载到了本地存储,这样就不会随着镜像的删除而删除data数据。Docker中安装Mysql传送门
前言:说起我与云开的故事,得时间回溯一下。大概是在2021年的4,5月份吧,加入到云开发平台的使用以及云开发的相关活动中来。实际上我的阿里云认证是在2019年的4月份,最初还只是停留在ECS的使用中,随着认识了云开发平台,丰富了阿里云的云产品见识以及使用。我与云开发的故事在接触云开发平台之前,大都从事的是传统方向的开发,对云开发也没有什么概念。近些年,大家都在讲云原生、云开发,正好这个云开发平台有了学习练手的机会。不知不觉的,阿里云平台陪伴了我有3年的时间,而云开发平台也有1年的时间了。从不知道、不会用的云产品的小白,在一点点的成长。同时,在社群中遇到的志同道合的小伙伴,有时指点迷津,有时开创先锋,卧虎藏龙,协同大家在云开发平台中驰骋遨游。一次次的奖品诱惑,一次次的参与云开发活动、征文、问答等等,使我深陷其中。打开开发者社区的课程中心,慢慢的都是惊喜,一个个的体验场景、一步步的操作步骤,免费的体验资源,这怎么不让人喜欢。更有甚至,自己需要一个ECS资源环境,完全可以使用体验资源创建一个有限时长的ECS去练手。我与云开发平台初识记得在云开发平台建立自己的一个团队的时候,还有点犹豫,这么多的认证,这么多的产品全线开通,我对这些产品又不熟悉,产生大量费用该怎么办啊。需要API 网关/API Gateway、函数计算/Function Compute、日志服务/Log Service、容器镜像服务/Alicloud Container Registry、对象存储/Object Storage Service、专有网络/Virtual Private Cloud,需要创建那么多的应用,自己不会管理该怎么办,但抱着试试的心态,就去创建开通了。后来,部署完应用,及时的下线应用,在有限的体验额度内,完全可以做到零费用。这让我对云开发平台更加爱不释手,因为创建部署一个应用太容易,点点,换换,就完成了。过去的一年活动回溯一路走来,大大小小已经参加了很多次的活动,在有学习收获的同时,还有奖品礼物可以获取(当然,可能是奔着礼物来的~~~每次都卡在10点去刷新抢礼物)。部署“你的第一个钉钉应用系统(4.12~4.20)”可能是参加的第一个活动其中部署了一个会议室预定系统,一个抽奖助手https://workbench.aliyun.com/activities/ding生而不凡-云原生应用开发节5.27~6.4其中部署了一个Hexo博客系统、一个H5魔改合成大西瓜的游戏https://workbench.aliyun.com/activities/fest琴动芳华6.30~7.7其中部署了一个线上版本的钢琴,体验了一把线上钢琴的乐趣当然收获了第一个天猫精灵boom,同时也给后面的“天气小蜜”做好了伏笔https://workbench.aliyun.com/activities/july温故知新-解压腕力球8.25~9.1https://workbench.aliyun.com/activities/score"AI"送你秋天里的第一件卫衣9.22~10.1自己动手部署了一个“天气小蜜”,体验到了云开发的乐趣,有了参与感https://workbench.aliyun.com/activities/aligenie致青春-云上毕业纪念册毕业季,留下自己的毕业留念https://developer.aliyun.com/ask/321737云开发小课当然不能忘记中间N节的云开发小课,云开发大佬给我们指点迷津https://workbench.aliyun.com/learning/chalktalk看到这些场景,会有些记忆犹新。这是尽自己最大的努力,翻箱倒柜,遍历出来的活动场景。自己的冰墩墩2022冬奥年,火爆、萌化了的冰墩墩让大家爱不释手。不管是软件行业还是硬件行业,都在表达着对冰墩墩的喜爱。刚好有这个时机,社区的小伙伴们提供的动画场景那就部署起来。错失冰墩墩,代码来补救:https://developer.aliyun.com/article/869776立体感的冰墩墩部署网页部署仅用来个人学习、研究和欣赏,如有侵权,联系删除~~~最后2022,我与云开发的故事还在继续,体验、学习的路上继续前行~~~
一、EMR离线数据分析E-MapReduce(简称"EMR")是云原生开源大数据平台,向客户提供简单易集成的Hadoop、Hive、Spark、Flink、Presto、Clickhouse、Delta、Hudi等开源大数据计算和存储引擎。-- 摘自 体验场景-背景知识 https://developer.aliyun.com/adc/scenario/175735954e19429cbb753cd547c00b5a产品的简单应用1.1 登录EMR集群1.1.1 搜索产品名称阿里云产品名称:开源大数据平台E-MapReduce1.1.2 在集群信息中找到集群的公网地址注意切换云产品资源所在的地域1.1.3 ssh连接ssh root@<公网ip>1.2 上传数据到HDFS1.2.1 创建HDFS目录 hdfs dfs -mkdir -p /data/student1.2.2 上传文件到Hadoop系统# 创建u.txt文件 vim u.txt # 编辑文件.说明:第一列表示userid,第二列表示movieid,第三列表示rating,第四列表示unixtime 196 242 3 881250949 186 302 3 891717742 22 377 1 878887116 244 51 2 880606923 166 346 1 886397596 298 474 4 884182806 115 265 2 881171488 253 465 5 891628467 305 451 3 886324817 6 86 3 883603013 62 257 2 879372434 286 1014 5 879781125 200 222 5 876042340 210 40 3 891035994 224 29 3 888104457 303 785 3 879485318 122 387 5 879270459 194 274 2 879539794 291 1042 4 874834944 234 1184 2 892079237 119 392 4 886176814 167 486 4 892738452 299 144 4 877881320 291 118 2 874833878 308 1 4 887736532 95 546 2 879196566 38 95 5 892430094 102 768 2 883748450 63 277 4 875747401 160 234 5 876861185 50 246 3 877052329 301 98 4 882075827 225 193 4 879539727 290 88 4 880731963 97 194 3 884238860 157 274 4 886890835 181 1081 1 878962623 278 603 5 891295330 276 796 1 874791932 7 32 4 891350932 10 16 4 877888877 284 304 4 885329322 201 979 2 884114233 276 564 3 874791805 287 327 5 875333916 246 201 5 884921594 242 1137 5 879741196 249 241 5 879641194 99 4 5 886519097 178 332 3 882823437 251 100 4 886271884 81 432 2 876535131 260 322 4 890618898 25 181 5 885853415 59 196 5 888205088 72 679 2 880037164 87 384 4 879877127 290 143 5 880474293 42 423 5 881107687 292 515 4 881103977 115 20 3 881171009 20 288 1 879667584 201 219 4 884112673 13 526 3 882141053 246 919 4 884920949 138 26 5 879024232 167 232 1 892738341 60 427 5 883326620 57 304 5 883698581 223 274 4 891550094 189 512 4 893277702 243 15 3 879987440 92 1049 1 890251826 246 416 3 884923047 194 165 4 879546723 241 690 2 887249482 178 248 4 882823954 254 1444 3 886475558 293 5 3 888906576 127 229 5 884364867 225 237 5 879539643 299 229 3 878192429 225 480 5 879540748 276 54 3 874791025 291 144 5 874835091 222 366 4 878183381 267 518 5 878971773 42 403 3 881108684 11 111 4 891903862 95 625 4 888954412 8 338 4 879361873 162 25 4 877635573 87 1016 4 879876194 279 154 5 875296291 145 275 2 885557505 119 1153 5 874781198 62 498 4 879373848 62 382 3 879375537 28 209 4 881961214 135 23 4 879857765 32 294 3 883709863 90 382 5 891383835 286 208 4 877531942 293 685 3 888905170 216 144 4 880234639 166 328 5 886397722 # 上传文件u.txt到hadoop文件系统 hdfs dfs -put u.txt /data/student1.2.3 查看文件hdfs dfs -ls /data/student1.3 使用Hive创建表1.3.1 登录hive数据库hive1.3.2 创建user表CREATE TABLE emrusers ( userid INT, movieid INT, rating INT, unixtime STRING ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' ;1.3.3 从Hadoop文件系统加载数据到Hive数据表 LOAD DATA INPATH '/data/student/u.txt' INTO TABLE emrusers;1.4 对表进行操作1.4.1 查看5行表数据select * from emrusers limit 5;1.4.2 查询数据表中有多少数据select count(*) from emrusers;1.4.3 查询数据表中评级最高的三个电影select movieid,sum(rating) as rat from emrusers group by movieid order by rat desc limit 3;二、阿里云ElasticSearch快速搭建智能运维系统体验场景:https://developer.aliyun.com/adc/scenario/18d723b856564fcbafb365d121804588此场景体验,需要资源比较多,创建场景耗时比较长,慎入!!!使用过ElasticSearch查询,Kibana查询的应该不会陌生,此场景略过了~~~三、使用PAI基于协同过滤算法实现商品推荐体验场景:https://developer.aliyun.com/adc/scenario/de9047c2055643938aea1ff228d6b207果然,基于大数据的相关操作,都是需要耗费大量的服务器资源的写在最后:实话说,到最后这一期有点坚持不下来,就是大概的点了点,体验了一下。关于最后的这篇学习报告,也是想对整个实战营做个完结,坚持着大概的写了写。以凑齐五篇学习报告,对应5期实战营
一、k8s部署无状态的魔方游戏通过ACK集群部署一个无状态工作负载Deployment最主要的一步是从指定的位置拉取镜像 ,指定资源配置及服务端口和容器端口registry.cn-hangzhou.aliyuncs.com/acr-toolkit/ack-cube在网络-Service 功能菜单中找到创建的服务的外部端点至此,一个魔方游戏服务部署完成,可以通过外部端点进行去访问了后面体验了一下服务的监控应用运维管理中的Prometheus监控没有特定的场景需求,也就是去点吧点吧去看看热闹二、docker镜像管理2.1 安装docker# 添加docker依赖库 yum install -y yum-utils device-mapper-persistent-data lvm2 # 添加docker ce的软件源信息 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 安装docker ce yum makecache fast && yum -y install docker-ce # 启动docker服务 systemctl start docker # 配置镜像加速器,此处也可以换成自己的镜像加速器 tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://registry.docker-cn.com"] } EOF # 重启docker服务 systemctl restart docker2.2 创建Dockerfile省略创建Dockerfile具体过程基于Dockerfile文件去build,指定当前路径.docker build . -t demo:v1 # docker run 运行镜像 docker run -d -p 8000:80 demo:v1 # 删除镜像,需要先stop依赖于这个镜像的容器 docker rm -f $(docker ps -a | grep "demo:v1" | awk '{print $1}')2.3 镜像推送至远程仓库docker login --username="用户名" registry.cn-hangzhou.aliyuncs.com # 标记本地镜像,将其归入远程仓库 docker tag demo:v1 registry.cn-hangzhou.aliyuncs.com/space_test/demo:v1 # 将本地镜像推送至远程仓库 docker push registry.cn-hangzhou.aliyuncs.com/space_test/demo:v1 # 拉取指定版本的远程镜像 docker pull registry.cn-hangzhou.aliyuncs.com/space_test/demo:v1 # 运行拉取的指定镜像 docker run -d -p 8000:80 registry.cn-hangzhou.aliyuncs.com/space_test/demo:v1大概就是安装docker,创建Dockerfile,镜像制作docker build,容器运行docker run镜像标记docker tag,镜像删除docker rm,镜像推送docker push,镜像拉取docker pull三、混沌工程-故障演练(AHAS Chaos)对于这一部分,坑坑好多,一个是产品的操作界面迭代更新与体验手册的文档不太一样最致命的是根据体验手册进行不下去了,这是最无gai的~~~在创建AHAS应用时,菜单名称不一致,由应用目录变为了应用市场。只能说产品更新迭代的速度太快了。在部署AHAS服务时,操作也有一点小变化。这两者的变化都是小细节,影响不大。在创建故障演练时,下拉选择服务及服务组时,发现下拉选项是空的。开始第一次操作卡壳了,就放弃了~~~再后来操作时,就发现,可以选择服务了。实际上,右侧也给了提示“找不到应用”,选择“应用接入”在这个中就给出了解决的方案再到后面的操作,大都是在看新奇的热闹,毕竟没接触过,也是自己能力有限~~~再到后面的故障演练,创建了一个依赖治理的演练,扫描服务之间的依赖关系。下面的提示说:近1分钟依赖关系无变化,可停止分析然而,自己演练始终没有出现依赖关系,后面的就放弃了每一期的结束,最刺激的应该是秒没的鼠标领取活动但是这一期出了些许的故障,正常发放是在每一期的最后一天的10点卡点出现这一期抢了个寂寞,有点懵。先是0点之后就能领取了,发现这个问题之后,就将剩下的,放到了10点发放,然而对于我这个蹲点刷新的来说,10点刷新了个寂寞。站到将来去回看历史的角度,才知道不是在10点卡点发放的,往后推迟了半分钟的样子~~~由此,给自己一个警醒,对于并发的“抢”活动来说,像优惠券的“抢”活动,设置好活动的开始时间,并且做好数量的准确,做到不少发,不超发~~~不然会给用户带来surprise的体验
前言:以前玩的虚拟机都是通过宿主机动态分配的ip,就导致ip地址会发生变化,不太方便(也是自己太菜)。然后,近来就进行手动设置了ip,设置完成后就发现,可以ping通一个ip地址,但不能ping通域名。然后就怀疑是DNS的问题,就去找啊找。东找找,西试试的,搞定了~~~虚拟机网卡的配置文件(/etc/sysconfig/network-scripts/ifcfg-ens33,此处的ifcfg-ens33是你的网卡名字)的设置如下TYPE="Ethernet" BOOTPROTO="none" IPADDR="192.168.126.12" PREFIX="24" GATEWAY="192.168.126.1" DEFROUTE="yes" IPV4_FAILURE_FATAL="no" IPV6INIT="yes" IPV6_AUTOCONF="yes" IPV6_DEFROUTE="yes" IPV6_PEERDNS="yes" IPV6_PEERROUTES="yes" IPV6_FAILURE_FATAL="no" IPV6_ADDR_GEN_MODE="stable-privacy" NAME="ens33" UUID="da8ec5ac-5651-4721-aded-be23900aa539" DEVICE="ens33" ONBOOT="yes由于配置文件没有设置默认DNS所以出现标题写的问题。在网卡的配置文件中增加DNS服务器地址如下TYPE="Ethernet" BOOTPROTO="none" IPADDR="192.168.126.12" PREFIX="24" GATEWAY="192.168.126.1" DEFROUTE="yes" IPV4_FAILURE_FATAL="no" IPV6INIT="yes" IPV6_AUTOCONF="yes" IPV6_DEFROUTE="yes" IPV6_PEERDNS="yes" IPV6_PEERROUTES="yes" IPV6_FAILURE_FATAL="no" IPV6_ADDR_GEN_MODE="stable-privacy" NAME="ens33" UUID="da8ec5ac-5651-4721-aded-be23900aa539" DEVICE="ens33" DNS1="8.8.8.8" DNS2="8.8.4.4" ONBOOT="yesservice network restart
服务器环境:centos7一、docker安装1.1 安装摘自:https://developer.aliyun.com/article/110806# step 1: 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3: 更新并安装 Docker-CE sudo yum makecache fast sudo yum -y install docker-ce # Step 4: 开启Docker服务 sudo service docker start 注意:其他注意事项在下面的注释中 # 官方软件源默认启用了最新的软件,您可以通过编辑软件源的方式获取各个版本的软件包。例如官方并没有将测试版本的软件源置为可用,你可以通过以下方式开启。同理可以开启各种测试版本等。 # vim /etc/yum.repos.d/docker-ce.repo # 将 [docker-ce-test] 下方的 enabled=0 修改为 enabled=1 # # 安装指定版本的Docker-CE: # Step 1: 查找Docker-CE的版本: # yum list docker-ce.x86_64 --showduplicates | sort -r # Loading mirror speeds from cached hostfile # Loaded plugins: branch, fastestmirror, langpacks # docker-ce.x86_64 17.03.1.ce-1.el7.centos docker-ce-stable # docker-ce.x86_64 17.03.1.ce-1.el7.centos @docker-ce-stable # docker-ce.x86_64 17.03.0.ce-1.el7.centos docker-ce-stable # Available Packages # Step2 : 安装指定版本的Docker-CE: (VERSION 例如上面的 17.03.0.ce.1-1.el7.centos) # sudo yum -y install docker-ce-[VERSION] # 注意:在某些版本之后,docker-ce安装出现了其他依赖包,如果安装失败的话请关注错误信息。例如 docker-ce 17.03 之后,需要先安装 docker-ce-selinux。 # yum list docker-ce-selinux- --showduplicates | sort -r # sudo yum -y install docker-ce-selinux-[VERSION] # 通过经典网络、VPC网络内网安装时,用以下命令替换Step 2中的命令 # 经典网络: # sudo yum-config-manager --add-repo http://mirrors.aliyuncs.com/docker-ce/linux/centos/docker-ce.repo # VPC网络: # sudo yum-config-manager --add-repo http://mirrors.could.aliyuncs.com/docker-ce/linux/centos/docker-ce.repo1.2 配置加速镜像配置阿里云的加速镜像https://cr.console.aliyun.com/sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["加速镜像地址"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker二、docker中安装mysql2.1 查询mysql的各个版本docker search mysql2.2 拉取镜像通过pull命令去拉取镜像,默认拉取的是latest版本,可以通过冒号来指定版本,如拉取5.7版本的mysqldocker pull mysql:5.7可以通过https://hub.docker.com/_/mysql去查询版本号2.3 查看拉取的镜像docker images2.4 新建一个mysql的配置文件要运行mysql,执行docker run一下就可以了但是,我们希望做一些配置,如:指定数据库data的存放位置,不区分大小写这是我们新建一个my.cnfmkdir -p /data/mysql/config vim my.cnf在my.cnf中添加参数[mysqld] user=mysql sql_mode=STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION lower_case_table_names=1 event_scheduler=ON default-time-zone = '+8:00'其中event_scheduler=ON 表示开启事件支持lower_case_table_names=1 表示数据库不区分大小写default-time-zone = '+8:00' 表示使用中国时区2.5 docker运行mysqldocker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql -v /data/mysql/config/my.cnf:/etc/mysql/my.cnf -v /data/mysql/db:/var/lib/mysql mysql:5.7各个参数的说明run:运行一个容器-d:设置容器运行模式为后台运行-p:进行端口映射,用于暴露给外界让其访问-e:初始化用户密码--name:自定义容器名称-v:挂载第一个-v:挂载我们自定义的配置文件第二个-v:挂载数据库的data存放位置2.5.1 docker中mysql的时区修改不知道为什么上面的指定参数default-time-zone = '+8:00'没有生效找了其他几种方法吧启动容器时设置-e TZ=Asia/Shanghai容器内永久修改# 进入容器 docker exec -it mysql bash # 查看当前时区 date -R # 修改时区 cp /usr/share/zoneinfo/PRC /etc/localtime # 或者 ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime # 退出容器 exit # 重启容器 docker restart mysqlmysql中临时修改,重启失效# 查看时间 select now(); # 修改为北京时间 mysql> set global time_zone = '+8:00'; Query OK, 0 rows affected (0.00 sec) mysql> set time_zone = '+8:00'; Query OK, 0 rows affected (0.00 sec) mysql> flush privileges; Query OK, 0 rows affected (0.00 sec)2.6 查看运行的容器使用docker ps查看,类似于linux的ps,查看全部使用docker ps -a进入容器内部,使用mysql的相关命令# 进入容器名称为mysql的内部 docker exec -it mysql /bin/bash mysql -uroot -p # 在mysql的命令行中进行操作 show databases; # 退出 exit2.7 mysql相关配置2.7.1 创建一个用户创建一个用户,如:testuser/123456# 限定的IP地址,可以用通配符%替换,代表任何IP都可以 mysql> CREATE USER 'testuser'@'限定的IP地址' IDENTIFIED BY '123456'; 2 Query OK, 0 rows affected (0.02 sec) # 设置这个用户的登录权限 # 设置testuser用户允许本地登录 grant all privileges on *.* to testuser@localhost identified by "123456"; # 设置testuser用户允许任何任何ip登录 grant all privileges on *.* to testuser@"%" identified by "123456"; # 进行flush # mysql新设置用户或更改密码后需用flush privileges刷新MySQL的系统权限相关表 # 否则会出现拒绝访问,还有一种方法,就是重新启动mysql服务器,来使新设置生效 flush privileges;2.7.2 设置用户的数据库访问权限# 设置用户testuser,只能访问数据库test_db的表user_infor # *.*代表任何数据库的任何表 # 数据库中的其他表均不能访问 ; grant all privileges on test_db.user_infor to testuser@localhost identified by "123456";2.7.3 设置用户操作权限# 设置用户testuser,拥有所有的操作权限,也就是管理员 ; grant all privileges on *.* to testuser@localhost identified by "123456" WITH GRANT OPTION; # 设置用户增删改查的权限 grant select,insert,update,delete on *.* to testuser@localhost identified by "123456" ;2.7.4 关闭root用户的远程登录# 切换到mysql数据库 use mysql; delete from user where user = 'root' and host = '%'; select user,host from user; #刷新权限 flush privileges ;2.8 其他相关# 查看容器 docker container ls # 查看所有容器 docker container ls -a # 删除容器 docker rm [容器名称or容器的id] docker rm mysql docker rm 88f9f37e7de8 # 删除容器前先关闭容器 docker stop mysql # 删除镜像,同样删除镜像之前需要stop依赖这个镜像的容器 docker rmi [image id]2.9 在安装过程中遇到的坑在run一个容器时,发现没有网络查了一番之后,说是没有开启转发,网桥配置完之后,需要开启转发,不然容器启动后,就没有网络,配置/etc/sysctl.conf,添加net.ipv4.ip_forward=1# 编辑文件 vim /etc/sysctl.conf # 配置转发 net.ipv4.ip_forward=1 # 重启服务,让配置生效 systemctl restart network # 查看是否成功,如果返回为“net.ipv4.ip_forward = 1”则表示成功 sysctl net.ipv4.ip_forward # 重启docker服务 service docker restart
前言:自己也动手搭建过数据库,只不过是通过安装包的方式搭建的,而非yum源的方式。安装过程也是曲曲折折,一些心酸~~~如果是自己玩一下,用yum源的方式安装确实比较方便。而对于生产环境来说,不知道也是否采用的是这种方式。一、Mysql的快速部署大致分为了:更新yum源、通过yum源安装、启动服务、mysql相关配置、增删改查简单使用1.1 更新yum源rpm -Uvh http://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm1.2 通过yum源安装yum -y install mysql-community-server --nogpgcheck查看版本号mysql -V1.3 启动服务systemctl start mysqld # 设置开机自启动 systemctl enable mysqld1.4 mysql的相关配置1.4.1 获取初始密码# 获取/var/log/mysqld.log下的root初始密码 grep 'temporary password' /var/log/mysqld.log1.4.2 root用户登录mysql -uroot -p1.4.3 相关配置# 修改root的初始密码 # 修改密码安全策略为低(只校验密码长度,至少8位)。 set global validate_password_policy=0; ALTER USER 'root'@'localhost' IDENTIFIED BY '12345678'; # 授予root用户远程管理权限(这个一般不建议,只可以本地登录) GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '12345678';1.5 增删改查简单使用# 创建数据库 create database test; # 查看所有数据库 show databases; # 切换数据库 use test; # 查看该库下的所有表 show tables; # 建表 create table test1 (id int,name char(20)); # 插入数据 insert into test1 values(1,'zhangsan'); # 查看数据 select * from test1; # 更新数据 update test1 set name = 'lisi' where id = 1; # 删除数据 delete from test1 where id = 1; # 删除表 drop table test1; # 删除数据库 drop database test;linux界面中对mysql数据库备份mysqldump -uroot -p test > test.sql导入数据库mysql -uroot -p -Dtest < test.sql在数据库中导入mysql -uroot -p12345678use test;source /root/test.sql二、Mysql与RDS的结合应用基于一个本身已经搭建好的mysql数据库(上面的快速部署)一些已经的初始化工作,如:初始化好的bakery将Mysql中的数据迁移至RDS上来2.1 基于bakery的查询操作(轻车熟路~~~)show databases; use bakery; show columns from customer; show columns from product; select * from customer; select * from product;2.2 和RDS的结合大致上分为几部分,创建账号、创建数据库、使用dts做数据迁移2.2.1 创建账号RDS控制台中账号管理中创建账号,以供连接RDS的数据库2.2.2 创建数据库RDS控制台中数据库管理中创建数据库,指定创建的账号连接,以供将Mysql中的数据库数据迁移至该库中2.2.2 dts数据迁移控制台中选择数据迁移,配置迁移任务,填写相关连接信息,完成迁移~~~至此,将mysql中的数据迁移至了RDS上了2.3 RDS中的数据查询登录数据库,使用创建账号步骤中创建的账号进行登录,做基本的 sql 使用、查询其实就是将我们平常使用的数据库连接工具(如:Navicat等),只不过这个地方可能有些高阶的用法三、ECS和RDS搭建wordpress博客大致上分为了RDS中创建账号、数据库,部署Apache服务、php服务、wordpress服务3.1 创建账号、数据库不知道是因为老版本和新版本的rds操作界面不一样的原因,还是其他的在这个地方遇到了一个小坑体验手册中在创建数据库时,授权账号时,有一个账号权限,这里没有这个界面,一开始有没管,后面在访问wordpress时,就说创建的账号test_user没有访问wordpress数据库的权限,然后就在账号管理中进行权限授权了一下,说白了,就是给账号授权数据库的操作,然后问题就解决了~~~记录一下问题3.2 部署Apache服务安装Apache服务及其扩展包yum -y install httpd httpd-manual mod_ssl mod_perl mod_auth_mysql启动Apache服务systemctl start httpd.service访问http://<公网ip>,查看测试页面3.3 部署php环境安装yum -y install php php-mysql gd php-gd gd-devel php-xml php-common php-mbstring php-ldap php-pear php-xmlrpc php-imap创建测试页面echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php重启Apache服务systemctl restart httpd访问http:///phpinfo.php,查看php是否安装成功3.4 部署wordpress服务安装wordpressyum -y install wordpress修改wordpress的配置文件a. 修改wp-config.php指向路径为绝对路径# 进入/usr/share/wordpress目录。 cd /usr/share/wordpress # 修改路径。 ln -snf /etc/wordpress/wp-config.php wp-config.php # 查看修改后的目录结构。 llb. 移动WordPress到Apache根目录# 在Apache的根目录/var/www/html下,创建一个wp-blog文件夹。 mkdir /var/www/html/wp-blog mv * /var/www/html/wp-blog/c. 修改wp-config.php配置文件,指定数据库相关的连接信息# 指定数据库名称为之前创建的数据库wordpress sed -i 's/database_name_here/wordpress/' /var/www/html/wp-blog/wp-config.php # 指定连接数据库的用户为之前创建的用户test_user sed -i 's/username_here/test_user/' /var/www/html/wp-blog/wp-config.php # 指定连接数据库的用户密码为之前创建的用户密码Password1213 sed -i 's/password_here/Password1213/' /var/www/html/wp-blog/wp-config.php # 指定rds的连接地址,在rds的控制台中可以查询 sed -i 's/localhost/数据库地址/' /var/www/html/wp-blog/wp-config.phpd. 查看配置文件是否修改成功cat -n /var/www/html/wp-blog/wp-config.php重启Apache服务systemctl restart httpd访问http:///wp-blog/wp-admin/install.php,初始化WordPress在这里遇到了上面说的账号访问数据库的权限问题这一期中,收获了RDS、Mysql相关的操作
一、Windows 10 系统下同步阿里云校时服务器1.1 右键点击右下角的时间,然后点击 调整日期/时间1.2 日期和时间 窗口中,选择 添加不同时区的时钟1.3 日期和时间 窗口中,选择 Internet时间 栏目下的 更改设置1.4 选择 与Internet时间服务器同步,填写 ntp 服务器这里使用的是阿里云的ntp更多服务器地址,参考https://help.aliyun.com/document_detail/92704.html1.5 若要和其他 ntp 服务器时间同步,自行查找其他 ntp 服务器地址二、Windows Server 操作系统配置 NTP 服务详情参见 阿里云 云服务器ECS 中,配置Windows实例NTP服务https://help.aliyun.com/document_detail/51890.html三、其他参见:https://help.aliyun.com/document_detail/187600.htmlhttps://help.aliyun.com/document_detail/187016.htmlhttps://help.aliyun.com/document_detail/92803.html
前言:在参加这期实战营之前,自己其实也多多少少的会一些 Linux 的命令。大多都是日常工作中经常用到的,对于那些用不到的,总是记一遍忘一遍。往往复复,最终就记住了那么几个命令[捂脸哭]~~~参加完这期实战营之后,又记了一遍,对一些命令有了归类的归属,可以知道大体上分为哪几大类。一、 文本处理对于此类相关命令,大致上分为了日常使用过的和不经常使用过的1. 日常使用过的cat 查看一个文本内容较少的文件还是挺便捷的vim 更改个配置文件、编辑个文件内容都是离不开它的more 用的相对(less) 比较少less 用的比较多,排查个日志、错误信息是个不错的好帮手head 用的相对 比较少,只能说看具体的场景和个人的习惯吧tail 对于 -f 参数,使用的时候一直认为 实时打印 的意思。通过实战营,纠正这个记忆,是 追加最新的内容的 意思wc 本来是用作统计字符数的,与ls 搭配作为了统计文件数grep 关于grep的使用,只能是很强大,与许多命令的组合都会事半功倍2. 不经常使用的可能只是作为开发工程师来说,日常工作中用到的比较少吧~~~diff 用到过,用到的次数比较少吧state 好像是第一次接触,也可能是看过之后就忘了吧file 同上sed 同上awk 同上cut 同上tr 同上第一天结束后,还是收获了不少之前没有用过的用法。只能混个脸熟,相信后面会凭实力给忘掉的。大浪淘沙,能记住的都精华~~~二、 系统管理其中系统管理的命令又分为了 常用系统工作命令 和 系统状态监测命令1. 常用系统工作命令echo 使用起来还是挺便捷的date 作为开发工程师来说,使用这个的次数能比较少吧,当然也会有系统时间不准,需要校时的情况wget 下载一个网络资源还是挺方便的ps 就不用多说了,ps 搭配 grep 获取进程号,已经成了家常便饭top 动态地监视进程活动与系统负载等信息pidof 查询指定服务的进程号,get到一个新命令kill 相信再熟悉不过了,尤其是 kill -9killall 新get到的,删除指定进程名称下的所有进程,之前只会一个 kill2. 系统状态监测命令ifconfig 多用来获取ip,与 windows 的 ipconfig 一个字符之差uname 新get到的,以后不愁查询操作系统信息了uptime 新get到的,或许以后会用来查询服务器的运行时间?free 再熟悉不过了,与内存使用情况打交道 free -h 很友好who 用户相关,好像也算是新get到,之前只会一个 whoamilast 用户的最近登录信息,没太有好说的,好像history 记录历史命令,原来上箭头的使用 ↑ 的使用是依赖于这个history的记录啊,当执行 history -c 之后,↑ 就失效了,哈哈这一节收获的多是命令之间的组合使用,如:killall $(pidof crond)三、 磁盘管理文件的存储,离不开磁盘空间的存储,那关于磁盘管理的命令得略知一二1. df命令多使用 df -h 来查看各文件系统的容量2. du命令多使用 du -sh /* 来一步步的去排查每个目录下的所占用的容量最近,在使用 jenkins 发包的时候,就报出磁盘空间不足,导致发包失败然后通过 du -sh /* 来一步步的去排查占用情况最后发现历史的发包都给保存下来了导致了磁盘空不足,然后就去 rm -rf 了3. fdisk命令新get到的,这个命令之前压根都没有听说过,井底之蛙了~~~这一节中的du命令,来排查磁盘的使用情况,是个不错的帮手~~~四、 文件权限1. 操作文件(夹)相关的命令ls 显示指定目录下的内容,使用频率不少吧~~~pwd “我在哪”cd 切换工作目录,没有cd就相当于不能走路了touch 修改文件的属性,最开始接触的时候还以为是新建文件的意思,当然文件不存在也会新建文件,只不过理解了一半的意思,通过实战营把另一半的意思补全~~~mkdir 新建文件夹,-p 不存在就新建一个,可以变相的理解为递归创建文件夹rm 删除文件,关于这个 rm -rf /* 不能使用吧,不然会挨打的~~~cp 复制粘贴,要想复制粘贴的好,得需要它的参数来配合mv 本来是移动文件或文件夹的意思,只不过移动文件到当前文件夹下,就出现了重命名,最开始我就是这么记的rename 新get到的,批量命名,还得是它2. 文件权限相关chmod 以前看见chmod 744 [文件名],就照猫画虎,不知道啥意思,光知道是修改权限的意思,通过这次实战营补全了这一课chown 修改文件的属主和属组chgrp 修改文件的属组这个文件权限补上了之前的知识断层,收获满满。五、 文件管理最后一天的实战营涉及到了cat、cmp、diff、file、find这五个命令其中cat、diff、file这三个在文本处理中已概述了1. cmp命令感觉和diff相类似,不同之处在于,cmp只展示出第一处不同之处而diff会从头比较到尾2. find命令文件搜索命令,与之相同的还有一个locate,由于find搜索的范围比较大,耗费的资源比较多像diff和cmp,find和locate对比着讲会不会效果更好呢?综上,在这一期的 Linux 操作系统入门中,有收获,强行记忆了一波。
因为是体验嘛,当然得有体验优惠码优惠码获取:https://developer.aliyun.com/topic/wuying然后是激活优惠码:https://www.aliyun.com/daily-act/ecs/wuying_promotion下面是领取:起初是根据下面的 0元体验指南 进行的,但在进行的过程中还有些忐忑,生怕自己操作错了~~~一、无影云电脑的创建、登录1. 创建订单1.1 付费类型,选择“包年包月”1.2 名称自己随便起,只要符合验证规则就可以,这个名称主要是为了后续的显示。 就像这样子1.3 选择地域可以选择上海、杭州等等,后面也给出了提示,就近选择。这里就使用了默认的“上海”地域1.4 工作区需要新创建一个1.4.1 点击下面的创建工作区这里工作区的名称需要自己定义一下,其他都默认即可。这里的这个工作区的名称和上面第二步的名称写了同一个1.4.2 配置账号系统使用默认的“便捷账号”即可然后,工作区那选择刚刚创建的工作区就可以了1.5 购买云桌面数量选择“根据数值确定购买数量”,填写“1台”1.6 标签 是个啥,没研究,就跳过了1.7 安全策略管理 使用了默认的1.8 桌面模板管理 选择了“通用办公型-4核8G内存 windows2019 64位”1.9 购买时长,“1个月”1.10 自动续费,因为是体验,所以不勾选最终就这这个样子最后是,0元下单就可以。无影云电脑的创建有点慢,大概3~5分钟的样子,这个时候可以起身喝口茶了~~~创建完成后,会是绿色的“运行中”2. 创建用户这里收到的邮件,有点慢,大概在个10分钟左右,我的情况是这样子的3. 登录3.1 选择了浏览器页面登录地址:https://wuying.aliyun.com/polymerization 需要填的这两项,在邮件中都已经指出3.2 用户名、密码登录用户名、密码,在邮件中也已指出。用户名其实就是在管理控制台中添加用户时设置的用户名,密码是随机生成的,初次登录后,要就行修改3.3 连接 登录完成后,进行“连接”,就可以看到桌面了后面就是这个页面,再后面,就相当于一台在云上的电脑了4. 上网 这个地方有些不同,按理来说,设置完成后,就可以跟我们平常使用电脑一样了。 这里还需要在“无影桌面的控制台中的安全办公网络-互联网访问”开通互联网访问开通完成之后,就可以“baidu一下”、“google一下了”二、体验近几年,有了各种云,像提供云产品的阿里云、腾讯云、华为云等等;生活办公中的云笔记,有道云、印象笔记、石墨、语雀等等。再加上,如今的互联网、传统行业的业务也都在陆续上云。而如今,就连大家日常使用的电脑也在上云,实属有点不可思议。1. 第一感受使用无影云电脑,对于办公场景来说,应该还是挺好的。一台无影云电脑可以创建不同的用户,以此来给不同的用户使用,并且还可以按需创建,这样一台多人的复用,能够大大节省实际资源。但这样就会带来一个问题,用户还是得需要一台电脑(亦或许是pad)来进行连接,虽然能解决高配置的资源浪费,还是得人手分配电脑。好在可以工作软件安装到无影中,日常办公就登录无影进行,也能解决日常安装软件的繁琐过程。2. 第二感受大概浏览了一下其中的无影应用中心(就类似于手机中的软件商店),里面的应用没有那么多。不知道是因为考虑版权的原因,还是其他;也不知道通过浏览器安装到的软件(指应用中心中没有的软件,或者是那种破解软件)会不会到后面而被追究。这些还没做过尝试。三、便捷体验到最后,方便了自己,参加了阿里云的开发者社区的相关活动,需要进行抢奖品时,尤其是奖品数量比较少,大家都会卡到一个点去刷新某个页面。其实,活动开始的时间就是通过阿里云的校时进行的,这样拿着云无影电脑的时间去卡活动开始时间,就是一个捷径。
前言:账号的注册其实是在2019年。最开始只是体验了 ECS,真正对阿里云的了解,是在2021年,一个是公司的业务上云;另外一个2021年参加了大量的阿里云的开发者社区的活动。一点点的去了解到阿里云的云产品,一个是能获取小奖品,另外还能学到云知识,认识志同道合的开发者。刚开始使用阿里云的时候,由于没有具体的使用场景,都要靠自己的摸索。而现在,一个个的体验场景,免费的云场景资源,可以让新手也能快速上手,这个地方还是要给阿里云点赞。1. 初识上云基础 ECS 的基本概念 远程访问ECS实例,部署应用。 使用管理控制台对 ECS 进行基本操作:重置 ECS 实例登陆密码并重启 ECS 实例。关于 ECS 的概念,记得在有一次的活动回答中,有一个问题是,ECS 的全称是什么?这里特此记录一下,云服务器(Elastic Compute Service, 简称 ECS )在这个实验中,对于使用过 ECS 的童鞋来学,还是比较简单的。当然,也是有所收获,之前都是通过xshell去连接这次收获了通过 ssh root@<ip> 去连接2. 动手实操快速搭建 LAMP 环境LAMP:Linux+Apache+MySQL+PHP通过实验,学习到了基于 ECS 实例快速搭建一套LAMP环境对于这个实验,手拿把掐,因为之前就已经体验过多次了。3. ECS 部署 MySQL 数据库安装 MySQL配置 MySQL 服务。远程访问 MySQL 数据库。在这一部分中,收获禁用 root 账号远程登录数据库、创建新用户去授权远程登录数据库最眼前一亮的是通过 DMS数据管理服务 来管理 ECS 自建数据库,之前都是通过其他的连接工具去连接的,又get到了一个亮点。4. 直播带练云起实验室https://developer.aliyun.com/adc/labs/?spm=a2c6h.12873622.0.0.665b661eFgNazd4.1 workerbench 远程登录 ECS,快速搭建 Docker 环境实验地址:https://developer.aliyun.com/adc/scenario/9fd79b8711984e309f20d82bc65a26fa?spm=a2c6h.13858375.0.0.652979a9cPsp7m安装社区版 Docker CE配置阿里云镜像仓库(镜像加速)Docker 安装 Nginx 服务http://<公网地址>:8080/访问4.2 从零搭建 Spring Boot 的 Hello World使用 IntelliJ IDEA 搭建一个简单 Spring Boot 项目,并将这个项目部署到 ECS 上实验地址:https://developer.aliyun.com/adc/scenario/af334c34023e423785cdf06d9a92d692?spm=a2c6h.13858375.0.0.652979a9cPsp7m这个实验,相信干过 java 开发的童鞋们来说,无比的简单5. 使用 PolarDB 和 ECS 搭建门户网站这个实验之前还是没有接触过,对于 PolarDB 这个产品还是第一次接触。在这个场景中还是踩到了两个坑一个是,数据库无法正确安装然后就去网上百度,兜兜转转,通过下面解决了vim /etc/yum.repos.d/mysql-community.repo 将5.7版本的配置成enable=1和gphcheck=0后面就能愉快的玩耍了还有另外一种方案是,没验证过,后来看到其他童鞋给的推荐sed -i s/gpgcheck=1/gpgcheck=0/g /etc/yum.repos.d/mysql-community.repo sed -i s/gpgcheck=1/gpgcheck=0/g /etc/yum.repos.d/mysql-community-source.repo二个是,找数据库的连接地址,在集群详情页面下方没有找到,最后在云产品资源中找到。后面就一马平川,顺利完成这个门户网站的部署及展示。小欢喜~~~6. 最后的倔强建议后期的实战营活动可以丰富一下场景甚至可以在简单的场景基础上慢慢将场景复杂化最后:场景地址https://developer.aliyun.com/adc/series/wintercamp
第一种,实现Runnable接口public class MyThread implements Runnable { @Override public void run() { System.out.println("hello world"); } public static void main(String[] args) { MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start(); } }第二种,继承Thread类public class MyThread1 extends Thread { @Override public void run() { System.out.println("hello"); } public static void main(String[] args) { new MyThread1().start(); } }第三种,实现Callable接口public class MyThread2 implements Callable<String> { @Override public String call() throws Exception { System.out.println("call----"); return "call"; } public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<String> futureTask = new FutureTask<>(new MyThread2()); new Thread(futureTask, "A").start(); System.out.println(futureTask.get()); } }第四种,通过线程池获取线程开发规范手册中推荐使用的不要显式创建线程,请使用线程池。当然线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。Executors返回的线程池对象的弊端如下:1)FixedThreadPool和SingleThreadPool允许的请求队列长度为Integer.MAX_VALUE(约为21亿),可能对堆积大量的请求,从而导致OOM2)CacheThreadPool和ScheduledThreadPool允许创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM/** * 7个参数 * 核心线程池大小 * 最大核心线程池大小 * 超时了没有人调用就会释放 * 超时单位 * 阻塞队列 * 线程工厂:创建线程的,一般不用动Executors.defaultThreadFactory() * 拒绝策略 */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handle) { }四种拒绝策略:new ThreadPoolExecutor.AbortPolicy() // 池满了,还有进来的,不处理,抛出异常new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!线程池的最大的大小如何设置?CPU 密集型,几核,就是几,可以保持CPU的效率最高!IO 密集型,判断程序中十分耗IO的线程获取CPU的核数System.out.println(Runtime.getRuntime().availableProcessors());
1. starter工程的命名starter 是一个开箱即用的组件,减少不必要的重复代码,重复配置。例如,redis 的 starter,spring-boot-starter-data-redis Spring 官方定义的 starter 通常命名遵循的格式为 spring-boot-starter-{name}。例如 spring-boot-starter-web。非官方 starter 命名应遵循 {name}-spring-boot-starter 的格式。例如,dubbo-spring-boot-starter。2. 需求定义 @AspectLog 注解,用于标注需要打印执行时间的方法3. 实现3.1 创建一个SpringBoot项目这里项目名称为 aspectlog-spring-boot-starter3.2 导入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>关于 spring-boot-configuration-processor 的说明,引自 springBoot 官方文档Spring Boot uses an annotation processor to collect the conditions on auto-configurations in a metadata file ( META-INF/spring-autoconfigure-metadata.properties ). If that file is present, it is used to eagerly filter auto-configurations that do not match, which will improve startup time. It is recommended to add the following dependency in a module that contains auto-configurations大概意思就是:写 starter 时,在 pom 中配置 spring-boot-configuration-processor,在编译时会自动收集配置类的条件,写到一个 META-INF/spring-configuration-metadata.properties 文件中3.3 编写自动配置逻辑3.3.1 各种 ConditionsConditions描述@ConditionalOnBean在存在某个 bean 的时候@ConditionalOnMissingBean不存在某个 bean 的时候@ConditionalOnClass当前 classpath 可以找到某个类型的类时@ConditionalOnMissingClass当前 classpath 不可以找到某个类型的类时@ConditionalOnResource当前 classpath 是否存在某个资源文件@ConditionalOnProperty当前 jvm 是否包含某个系统属性为某个值@ConditionalOnWebApplication当前 spring context 是 web 应用程序才加载@ConditionalOnNotWebApplication不是web应用才加载这里选用 @ConditionalOnProperty。即配置文件中的属性aspectLog.enable=true,才加载我们的配置类。3.3.2 定义 @AspectLog 注解该注解用于标注需要打印执行时间的方法package org.example.aspectlog.interfaces; import java.lang.annotation.*; /** * 定义AspectLog注解,该注解用于标注需要打印执行时间的方法。 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AspectLog { }3.3.3 定义配置文件类package org.example.aspectlog.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = AspectLogProperties.PREFIX) public class AspectLogProperties { public static final String PREFIX = "aspectlog"; private boolean enable; public boolean isEnable() { return enable; } public void setEnable(boolean enable) { this.enable = enable; } }3.3.4 定义自动配置类package org.example.aspectlog.configuration; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.core.PriorityOrdered; @Aspect @EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true) @EnableConfigurationProperties(AspectLogProperties.class) @Configuration @ConditionalOnProperty(prefix = AspectLogProperties.PREFIX, name = "enable", havingValue = "true", matchIfMissing = true) public class AspectLogAutoConfiguration implements PriorityOrdered { private static final Logger LOGGER = LoggerFactory.getLogger(AspectLogAutoConfiguration.class); @Around("@annotation(org.example.aspectlog.interfaces.AspectLog)") public Object isOpen(ProceedingJoinPoint pjp) throws Throwable { String taskName = pjp.getSignature().toString().substring(pjp.getSignature().toString().indexOf(" "), pjp.getSignature().toString().indexOf("(")); taskName = taskName.trim(); long time = System.currentTimeMillis(); Object result = pjp.proceed(); LOGGER.info("method:{} run:{} ms", taskName, (System.currentTimeMillis() - time)); return result; } @Override public int getOrder() { // 保证事务等切面先执行 return Integer.MAX_VALUE; } }上面 @ConditionalOnProperty 中的参数说明prefix = AspectLogProperties.PREFIX,即AspectLogProperties中常量属性PREFIX="aspectlog"name = "enable",name的名称为enable时havingValue = "true",上面的name为enable的值为true时才开启matchIfMissing = true,匹配不到对应的属性的时也开启即:当配置文件有aspectLog.enable=true时开启,如果配置文件没有设置aspectLog.enable也开启。3.4 配置 META-INF/spring.factoriesMETA-INF/spring.factories是spring的工厂机制,在这个文件中定义的类,都会被自动加载。多个配置使用逗号分割,换行用\org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.example.aspectlog.configuration.AspectLogAutoConfiguration3.5 打包测试3.5.1 目录结构3.5.2 mvn install到本地3.5.3 其他项目中引用测试3.5.3.1 pom引用<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>aspectlog-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>3.5.3.2 测试代码@RestController @RequestMapping("/test") public class TestController { @AspectLog @GetMapping("/test") public String test () throws InterruptedException { return "success"; } }3.5.3.3 测试结果
1. 四大核心函数式接口1.1 Consumer消费型接口,有T类型的入参,无反参void accept(T t);1.2 Supplier供给型接口,无入参,有T类型的反参T get();1.3 Function函数式接口,有T类型的入参,有R类型的反参R apply(T t);1.4 Predicate断言型接口,有T类型的入参,有boolean类型的反参boolean test(T t);2. 其他函数接口2.1 BiConsumer两个入参的消费型接口,T,Uvoid accept(T t, U u);2.2 BiFunction两个入参的函数式接口,入参T,U,反参RR apply(T t, U u);2.3 UnaryOperator对参数类型T进行操作,入参T,反参T,为Function的子接口@FunctionalInterface public interface UnaryOperator<T> extends Function<T, T> { static <T> UnaryOperator<T> identity() { return t -> t; } }3. 实际应用3.1 痛点使用EasyExcel进行excel进行导入EasyExcel的 读操作 需要实现 ReadListener 接口以及定义一个映射 T 泛型存储数据行的实体。如:示例中的 DemoDataListener 和 DemoDatapublic class DemoDataListener implements ReadListener<DemoData> { // 省略具体的方法实现 }由于每次导入都要写一个Listener,非常麻烦,就想有没有一个能减少劳动力的方法呢?3.2 实现解决就利用了消费型接口 Consumer<T> 去做了实现3.2.1 实现ReadListener,这里实现的是抽象类AnalysisEventListener,也是一个ReadListenerpublic class EasyExcelConsumerListener<T> extends AnalysisEventListener<T> { private final List<T> list; private final Consumer<List<T>> consumer; public EasyExcelConsumerListener(Consumer<List<T>> consumer){ this.consumer = consumer; list = new ArrayList<>(); } @Override public void invoke(T data, AnalysisContext context) { list.add(data); } @Override public void doAfterAllAnalysed(AnalysisContext context) { consumer.accept(list); } }3.2.2 扩展EasyExcelpublic class EasyExcelUtil extends EasyExcel { private EasyExcelUtil() { } public static <T> ExcelReaderBuilder read(InputStream inputStream, Class<T> head, Consumer<List<T>> consumer) { return read(inputStream, head, new EasyExcelConsumerListener<>(consumer)); } } 3.2.3 使用在 Controller 层的代码实现,其中 ExcelData.class 是需要映射的数据实体类,这个映射实体类好像不能投机取巧了~~~@RestContoller @RequestMapping("/excel") public class ExcelController { @Autowired private IExcelService service; @GetMapping("/importExcel") public R importExcel(MultipartFile file) throws IOException { // service层执行具体的importExcel方法实现 EasyExcelUtil.read(file.getInputStream(), ExcelData.class, service::importExcel).sheet().doRead(); return R.success("导入成功!"); } } 这样就可以关注具体的业务实现啦3.2.4 使用两个参数的 BiConsumer<T, U>,与上面的 Consumer<T> 异曲同工实现 Listener/** * 异步导入 * 由于获取不到当前登录用户,通过参数传进来 */ public class AsyncBiConsumerListener<T> extends AnalysisEventListener<T> { private final Long userId; private final List<T> list; private final BiConsumer<List<T>, Long> biConsumer; public AsyncBiConsumerListener(BiConsumer<List<T>, Long> biConsumer, Long userId){ this.userId = userId; this.biConsumer = biConsumer; list = new ArrayList<>(); } @Override public void invoke(T data, AnalysisContext context) { list.add(data); } @Override public void doAfterAllAnalysed(AnalysisContext context) { biConsumer.accept(list, userId); } }扩展 EasyExcelpublic class EasyExcelUtil extends EasyExcel { private EasyExcelUtil() { } public static <T> ExcelReaderBuilder read(InputStream inputStream, Class<T> head, Consumer<List<T>> consumer) { return read(inputStream, head, new EasyExcelConsumerListener<>(consumer)); } public static <T> ExcelReaderBuilder read(InputStream inputStream, Class<T> head, BiConsumer<List<T>, Long> biConsumer, Long userId) { return read(inputStream, head, new AsyncBiConsumerListener<>(biConsumer, userId)); } }使用public void importExcel(InputStream file, String key, Long userId) { try { EasyExcelUtil.read(file, ExcelData.class, service::importExcel, userId).sheet().doRead(); } catch (Exception ex) { log.error("异步导入异常:", ex); } }
2023年09月
2023年08月
2023年07月
2023年03月
2023年02月
2022年11月
2022年10月
2022年09月
2022年08月
2022年07月
2022年06月
2022年05月
2022年04月
2022年03月
2022年02月
2022年01月
2021年12月
1、哪些事情是你成为程序员之后才知道的?
这代码功能在我这是好使的,怎么线上就有问题了呢。99%的问题都是源于特殊情况
2、你觉得大众对程序员印象误解最深的是什么?
你的头发还在吗?你秃头吗?你有几件格子衫?
两者都重要,业务驱动技术的发展,业务逻辑能力好,更有利于技术上的实现。同时,技术能力也能更好的为业务服务
1.手撕代码是程序员的基本功的体现,只能算作是基本功之一。
死记硬背的代码记忆时间不会长,只有通过自己的理解之后才会记忆长久。
2.对于手撕代码来考验程序员的能力,我觉得考验的更是逻辑、理解能力。
3.对于java来说,常见有一些冒泡算法、单例模式等等
Hello World
不知不觉已经度过四年的“1024”了。每天码着26个字母,与同事吐槽着“需求”。
开发的主力军MateBook14,以及华为的周边设备,2k的屏幕
每天按得最多的当属这个RK987A,侧刻,红轴,用着还算顺手吧。
以及一些阿里社区的周边,鼠标垫,指尖陀螺,多美卡车模等等,还有三个“破相”的不倒翁。
工作之余多活动活动,能锻炼的珍惜一切锻炼的时间。
收到的周边:冬奥云小宝、云小宝定制抱枕、纪念衫等等
云小宝定制的马克杯了、鼠标垫、鼠标等等比较符合开发者的身份,^_^
不知不觉已加入开发者社区1118天了,在期间收获了训练营的学习、活动的奖励等等。
也希望阿里云社区越办越好
从5天实战Spring Boot2.5开始加入训练营,期间参加了大大小小的各种训练营,有的涉及到Spring,有的涉及Docker,有的涉及Kubernets,还有的涉及到微服务治理等等。
遇见自己熟悉的训练营,就去不断的夯实基础,将基础去体系化。遇见自己不擅长的训练营,就去了解新鲜事物。遇见自己正好想去学习的东西,恰好又有训练营,那就去好好的去学习。
在我的印象中有那么几个训练营比较深刻。 企业运维训练营之云原生与Kubernetes实战(第1期),初步了解学习了云原生与k8s的基础入门,尽管被其中的概念、组件等绕晕了,但是还是坚持下来了。训练营结束之后,就去翻看Kubernets的官网,一点点的去啃。 再到今年的PolarDB等相关的训练营,想想最后的结营考试,能去一个点一个点的去细抠着去学习,社群里面小伙伴们的一个比一个高的分数,激励着自己去冲击百分。 同时,参加训练营,对于自己的知识点文档话,强化了自己的MarkDown的语法格式,在开发者社区中不断积累着自己的学习博客。 此外,参加训练营的同时,不光能收学习到知识,还能收获通过自己努力得到的奖品。在遨游开发者社区的同时,还能结识一些志同道合的小伙伴,也能发现一些其他的工具&资源,乐在其中。 最后,一个开发者社区的氛围离不开广大开发者的共同营造,也离不开官方对社区的运营。让我们广大开发者们不断的努力营造氛围,让我们的开发者社区变得更好。