首页
归档
留言
友链
广告合作
壁纸
更多
美女主播
Search
1
博瑞GE车机升级/降级
5,607 阅读
2
Mac打印机设置黑白打印
4,936 阅读
3
修改elementUI中el-table树形结构图标
4,894 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,672 阅读
5
intelliJ Idea 2022.2.X破解
4,354 阅读
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Vue
Spring Cloud
Mac
MyBatis
WordPress
MacOS
asp.net
Element UI
Nacos
.Net
Spring Cloud Alibaba
MySQL
Mybatis-Plus
Typecho
jQuery
Java Script
IntelliJ IDEA
微信小程序
Laughing
累计撰写
627
篇文章
累计收到
1,421
条评论
首页
栏目
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
友链
广告合作
壁纸
美女主播
搜索到
627
篇与
的结果
2023-03-05
SprintBoot切面+Redis防止前端重复提交
最近项目上遇到重复提交的情况,虽然前端对按钮进行了禁用,但是不知道是什么原因,后端仍然接收到了多个请求,因为是分布式系统,所以不能简单的使用lock,最终考虑决定使用redis实现。一、环境准备MySql:测试数据库Redis:使用Redis实现Another Redis Desktop Manager:跟踪Redis信息ApiFox:模拟请求,单线程循环及多线程循环Spring Boot:2.7.4二、准备测试数据及接口2.1、创建表创建一个最简单的用户表,只包含id、name两列create table User ( id int null, name varchar(200) null );2.2、创建接口2.2.1、配置依赖及数据库、Redis连接信息项目依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cc.lisen</groupId> <artifactId>RepeatSubmit</artifactId> <version>0.0.1-SNAPSHOT</version> <name>RepeatSubmit</name> <description>RepeatSubmit</description> <properties> <java.version>8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.16</version> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project> yaml文件配置数据库及Redis连接信息spring: redis: host: 192.168.236.2 port: 6379 password: datasource: #使用阿里的Druid type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.236.2/TestRepeatSubmit?serverTimezone=UTC username: root password: root2.2.2、创建实体@Data @TableName("User") public class User { private Long id; private String name; }2.2.3、创建数据访问层public interface UserMapper extends BaseMapper<User> { }2.2.4、创建异常处理类@Data @NoArgsConstructor @AllArgsConstructor public class ResultRet<T> { private Integer code; private String msg; private T data; //成功码 public static final Integer SUCCESS_CODE = 200; //成功消息 public static final String SUCCESS_MSG = "SUCCESS"; //失败 public static final Integer ERROR_CODE = 201; public static final String ERROR_MSG = "系统异常,请联系管理员"; //没有权限的响应码 public static final Integer NO_AUTH_COOD = 999; //执行成功 public static <T> ResultRet<T> success(T data){ return new ResultRet<>(SUCCESS_CODE,SUCCESS_MSG,data); } //执行失败 public static <T> ResultRet failed(String msg){ msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg; return new ResultRet(ERROR_CODE,msg,""); } //传入错误码的方法 public static <T> ResultRet failed(int code,String msg){ msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg; return new ResultRet(code,msg,""); } //传入错误码的数据 public static <T> ResultRet failed(int code,String msg,T data){ msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg; return new ResultRet(code,msg,data); } }2.2.5、简单的全局异常@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = Throwable.class) public ResultRet handleException(Throwable throwable){ log.error("错误",throwable); return ResultRet.failed(500, throwable.getCause().getMessage()); } }2.2.6、配置模拟接口模拟一个get请求的接口,用户新增用户,orm框架使用mybatis-plus,使用最简单的插入@RestController @RequestMapping("/user") public class UserController { @Resource private UserMapper userMapper; @GetMapping("/add") public ResultRet<User> add() { User user = new User(); user.setId(1L); user.setName("张三"); userMapper.insert(user); return ResultRet.success(user); } }以上配置完成后,当我们访问/user/add接口时,肯定访问几次,数据库就会重复插入多少信息。三、改造接口,防止重复提交改造的原理起始很简单,我们前端访问接口时,首先在头部都会携带token信息,我们通过切面,拦截请求,获取到token及请求的url,拼接后作为redis的key值,通过redis锁的方式写入key值,如果写入成功,设置一个过期时间,在有效期时间内,多次请求,先判断redis中是否有对应的key,如果有,抛出异常,禁止再次写入。3.1、配置RedisTemplate@Configuration public class RedisConfig { @Bean @SuppressWarnings(value = { "unchecked", "rawtypes" }) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }3.2、增加Redis工具类@Component @Slf4j public class RedisUtils { @Resource private StringRedisTemplate stringRedisTemplate; /** * Redis分布式锁 * * @return 加锁成功返回true,否则返回false */ public boolean tryLock(String key, String value, long timeout) { Boolean isSuccess = stringRedisTemplate.opsForValue().setIfAbsent(key, value); //设置过期时间,防止死锁 if (Boolean.TRUE.equals(isSuccess)) { stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); } return Boolean.TRUE.equals(isSuccess); } /** * Redis 分布式锁释放 * * @param key * @param value */ public void unLock(String key, String value) { try { String currentValue = stringRedisTemplate.opsForValue().get(key); if (StringUtils.isNotEmpty(currentValue) && StringUtils.equals(currentValue, value)) { stringRedisTemplate.opsForValue().getOperations().delete(key); } } catch (Exception e) { //这个是我的自定义异常,你可以删了 log.info("报错了"); } } }3.3、添加注解@Target(ElementType.METHOD) // 注解只能用于方法 @Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期 @Documented public @interface RepeatSubmitAnnotation { /** * 防重复操作过期时间,默认1s */ long expireTime() default 1; }3.4、添加切面@Slf4j @Component @Aspect public class RepeatSubmitAspect { @Resource private RedisUtils redisUtils; /** * 定义切点 */ @Pointcut("@annotation(cc.lisen.repeatsubmit.annotation.RepeatSubmitAnnotation)") public void repeatSubmit() { } @Around("repeatSubmit()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); // 获取防重复提交注解 RepeatSubmitAnnotation annotation = method.getAnnotation(RepeatSubmitAnnotation.class); // 获取token当做key,小编这里是新后端项目获取不到哈,先写死 String token = request.getHeader("token"); if (StringUtils.isBlank(token)) { throw new RuntimeException("token不存在,请登录!"); } String url = request.getRequestURI(); /** * 通过前缀 + url + token 来生成redis上的 key * 可以在加上用户id,小编这里没办法获取,大家可以在项目中加上 */ String redisKey = "repeat_submit_key:" .concat(url) .concat(token); log.info("==========redisKey ====== {}", redisKey); boolean lock = redisUtils.tryLock(redisKey, redisKey, annotation.expireTime()); if (lock) { log.info("获取分布式锁成功"); try { //正常执行方法并返回 return joinPoint.proceed(); } catch (Throwable throwable) { throw new Throwable(throwable); } finally { //释放锁 // redisUtils.unLock(redisKey, redisKey); // System.out.println("释放分布式锁成功"); } } else { // 抛出异常 throw new Throwable("请勿重复提交"); } } }3.5、接口添加注解这里为了方便演示,我们把提交间隔时间设置为30s。@RestController @RequestMapping("/user") public class UserController { @Resource private UserMapper userMapper; @GetMapping("/add") @RepeatSubmitAnnotation(expireTime = 30L) public ResultRet<User> add() { User user = new User(); user.setId(1L); user.setName("张三"); userMapper.insert(user); return ResultRet.success(user); } }至此,我们所有的配置都完成了,接下来使用ApiFox模拟一下接口访问。3.6、模拟测试我们先把数据库及Redis清空(本来其实就是空的)配置好自动化测试接口3.6.1、单线程测试先模拟单线程操作,循环50次查看Redis,查看有一个key打开数据库,可以看到只成功插入了一条3.6.3、模拟多线程先把数据库清空,Redis等待过期后自动删除再次模拟,10个线程,循环10次此时查看数据库,仍然只有一条插入成功了
2023年03月05日
1,136 阅读
1 评论
0 点赞
2023-02-26
《狂飙》最新资源分享(含VIP)
https://pan.quark.cn/s/a46721a17d1b#/list/share
2023年02月26日
646 阅读
0 评论
0 点赞
2022-11-24
Spring Cloud Alibaba笔记修订版-第三章Nacos Discovery--服务治理
一、什么是服务治理服务治理是微服务架构中最核心最基本的模块,用于实现各个微服务的自动化注册与发现。服务注册:在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务的详细信息。并在注册中心形成一张服务清单,服务注册中心需要以心跳的方式去监测清单中的服务是否可用,若不可用,需要再服务清单中剔除不可用的服务。服务发现:服务调用方向服务注册中心咨询服务,保宁获取所有服务的实例清单,实现对具体服务实例的访问。通过上面的图会发现,除了微服务,还有一个组件是服务注册中心,它是微服务架构中非常重要的一个组件,在微服务架构里起到了一个协调者的作用。注册中心一般包含以下几个功能:服务发现服务注册:保存服务提供者和服务调用者信息服务订阅:服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者信息服务配置配置订阅:服务提供者和服务调用者订阅微服务相关配置配置下发:主动将配置推送给服务提供者和服务调用者服务健康检测检测服务提供者的健康状况,如果发现异常,执行服务剔除常见的服务注册中心包括:Zookeeper、Eureka、Consul、Nacos。Nacos是Spring Cloud Alibaba组件之一,负责服务注册发现和服务配置,因为我们使用Spring Cloud Alibaba,所以这里只介绍Nacos的使用。二、Nacos简介Nacos致力于帮助您发现、配置和管理微服务。Nacos提供了一组简单易用的特性及,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。三、搭建Nacos环境注意使用Nacos之前,需要先配置好Java环境变量。我这里使用的服务器环境是Ubuntu 20.04,以下Nacos安装使用均以此为准,目前Nacos最新版本是2.1.2Nacos下载地址:Releases · alibaba/nacos (github.com)这里只介绍Nacos的基本使用,具体集群等高级用法,可以自行查找相关资料。3.1、下载Nacos下载nacos-server-2.1.2.tar.gz后,加压到任意位置。3.2、Nacos数据库文件运行Nacos之前,需要将Nacos数据库配置文件导入,我这里使用的是MySql,我直接导入上面Demo里面的数据库了。MySql的语句如下/* * 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(20) 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');3.3、配置Nacos在conf文件夹下有一个application.properties,我们需要配置里面的数据库连接信息把大概34行往下的位置,取消注释并根据自己情况进行配置### If use MySQL as datasource: spring.datasource.platform=mysql ### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://127.0.0.1:3306/shop?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0=root db.password.0=root3.4、运行Nacos进入bin文件夹执行./startup.sh -m standaloneNacos启动后,浏览器输入localhost:8848/nacos默认用户名及密码都是nacos四、将商品微服务注册到Nacos我们改造商品微服务,以便支持Nacos4.1、修改配置文件增加Nacos依赖<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.4.0</version> </dependency>4.2、主类上注解@EnableDiscoveryClient@SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) @EnableDiscoveryClient public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); } }4.3、配置文件添加nacos服务的地址spring: cloud: nacos: discovery: server-addr: 192.168.236.2:8848修改后配置文件如下server: port: 8081 spring: cloud: nacos: discovery: server-addr: 192.168.236.2:8848 application: name: service-user datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.236.2/shop?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=true username: root password: root jpa: hibernate: #指定为update,每次启动项目检测表结构有变化的时候会新增字段,表不存在时会新建,如果指定create,则每次启动项目都会清空数据并删除表,再新建 ddl-auto: update naming: #指定jpa的自动表生成策略,驼峰自动映射为下划线格式 implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl # 默认false,在日志里显示执行的sql语句 show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect database: mysql database-platform: org.hibernate.dialect.MySQL5Dialect4.4、查看服务是否注册成功重新启动product微服务服务名就是我们配置文件配置的应用名称。五、将订单微服务注册到Nacos我们改造订单微服务,以便支持Nacos5.1、修改配置文件增加Nacos依赖<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.4.0</version> </dependency>5.2、主类上注解@EnableDiscoveryClient@SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) @EnableDiscoveryClient public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } } 5.3、配置文件添加nacos服务的地址spring: cloud: nacos: discovery: server-addr: 192.168.236.2:8848修改后配置文件如下server: port: 8091 spring: cloud: nacos: discovery: server-addr: 192.168.236.2:8848 application: name: service-user datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.236.2/shop?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=true username: root password: root jpa: hibernate: #指定为update,每次启动项目检测表结构有变化的时候会新增字段,表不存在时会新建,如果指定create,则每次启动项目都会清空数据并删除表,再新建 ddl-auto: update naming: #指定jpa的自动表生成策略,驼峰自动映射为下划线格式 implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl # 默认false,在日志里显示执行的sql语句 show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect database: mysql database-platform: org.hibernate.dialect.MySQL5Dialect5.4、查看服务是否注册成功重新启动product微服务服务名就是我们配置文件配置的应用名称。5.5、改造订单接口,实现微服务调用package cc.lisen.shop.order.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; @RestController @RequestMapping("order") @Slf4j public class OrderController { @Resource private RestTemplate restTemplate; @Resource private DiscoveryClient discoveryClient; @GetMapping("product/{id}") public Product order(@PathVariable("id") Integer productID) { List<ServiceInstance> serviceInstanceLList = discoveryClient.getInstances("service-product"); log.info("获取到服务:" + serviceInstanceLList.size()); //忽略下面可能导致的错误 ServiceInstance serviceInstance = serviceInstanceLList.get(0); return restTemplate.getForObject(serviceInstance.getUri() + "/product/1", Product.class); } }浏览器访问订单接口六、负载均衡通俗的讲,负载均衡就是将负载(工作任务、访问请求)进行分摊到多个操作单元(服务器、组件)上进行执行。根据负载均衡发生的位置不同,一般分为服务端负载均衡和客户端负载均衡。服务端负载均衡指的是发生在服务提供者一方,比如nginx负载均衡客户端负载均衡指的是发生在服务请求一方,也就是在发送请求之前已经选好了由那个实例处理请求。微服务调用关系中一般会选择客户端负载均衡。6.1、准备负载均衡环境为了实现负载均衡,我们需要准备至少两个服务,这里以商品服务为例。在上面的代码中,我们pom.xml文件都没有加入打包的插件,为了启动两个服务,我们需要现将程序打包成jar包。在shop-user、shop-product、shop-order三个模块都加入打包插件。 <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>6.2、准备商品微服务为了区分调用的服务,我们在商品服务输出一下端口号。package cc.lisen.shop.product.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import cc.lisen.shop.product.service.ProductService; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j public class ProductController { @Resource private ProductService productService; @Resource private Environment environment; @GetMapping("/product/{id}") public Product product(@PathVariable("id") Integer ID) { log.error("当前端口号:" + environment.getProperty("local.server.port")); return productService.findByID(ID); } } 然后将shop-product打包成jar包,通过一下命令,启动两个服务java -jar shop-product-1.0-SNAPSHOT.jar --server.port=8081 java -jar shop-product-1.0-SNAPSHOT.jar --server.port=8082此时打开Nacos,可以看到shop-product有两个服务此时,我们运行订单工程,访问接口http://127.0.0.1:8091/order/product/1多次刷新之后,可以用看到服务全部都打到了8081端口上。8082一条都没有,可见并没有实现负载均衡。6.3、基于Ribbon实现负载均衡Ribbon是Spring Cloud的一个组件,它可以让我们使用一个注解就能轻松搞定负载均衡。nacos 2021版本已经没有自带Ribbon的整合,所以需要引入另一个支持的jar包 loadbalancer早shop-order中引入loadbalancer实现Ribbon支持<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>在RestTemplate上注解上@LoadBalanced即可。package cc.lisen.shop.order.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class OrderConfiguration { @LoadBalanced @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } } 改造接口,通过服务名访问package cc.lisen.shop.order.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; @RestController @RequestMapping("order") @Slf4j public class OrderController { @Resource RestTemplate restTemplate; @Resource DiscoveryClient discoveryClient; @GetMapping("product/{id}") public Product order(@PathVariable("id") Integer productID) { String serviceName = "service-product"; return restTemplate.getForObject("http://"+serviceName + "/product/1", Product.class); } }此时,我们运行订单工程,访问接口http://127.0.0.1:8091/order/product/1多次刷新之后,可以用看到服务平均打到了8081和8082端口。Ribbon默认的均衡策略是轮训。Ribbon自带的负载均衡策略我们可以通过修改配置文件改变默认的负载均衡策略。Ribbon 已经在最新的Spring Cloud 版本中被废弃,Spring Cloud Loadbalancer 是官方正式推出的一款新负载均衡利器,在未来,LoadBalancer 很有可能取代Ribbon的地位成为新一代的负载均衡器6.4、基于Feign实现负载均衡Feign是Spring Cloud提供的一个声明式的伪HTTP客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加注解即可。Nacos很好的兼容了Feign,Feign默认集成了Ribbon,所以在Nacos下使用Feign默认就实现了负载均衡的效果。在进行一下代码之前,记得先移除6.3添加的Ribbon相关的代码我们改造shop-order工程,实现Feign的使用6.4.1、引入依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-loadbalancer</artifactId> </dependency>6.4.2、在主类上添加Feign注解package cc.lisen.shop.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) @EnableDiscoveryClient @EnableFeignClients public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } } 6.4.3、添加一个servicepackage cc.lisen.shop.order.Service; import cc.lisen.shop.common.entity.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient("service-product") public interface ProductService { @GetMapping("/product/{id}") Product findByID(@PathVariable("id") Integer id); }Feign调用服务的地址就是@FeignClient+@GetMapping(获取其他映射)的地址6.4.4、改造Controllerpackage cc.lisen.shop.order.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import cc.lisen.shop.order.Service.ProductService; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; @RestController @RequestMapping("order") @Slf4j public class OrderController { @Resource ProductService productService; @GetMapping("product/{id}") public Product order(@PathVariable("id") Integer productID) { return productService.findByID(productID); } } 此时,我们运行订单工程,访问接口http://127.0.0.1:8091/order/product/1多次刷新之后,可以用看到服务平均打到了8081和8082端口。6.4.5、修改轮训策略以前的Ribbon有多种负载均衡策略但LoadBalancer貌似只提供了两种负载均衡器,不指定的时候默认用的是轮询。RandomLoadBalancer 随机RoundRobinLoadBalancer 轮询添加LoadBalance配置类package cc.lisen.shop.order.config; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer; import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer; import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import javax.annotation.Resource; public class MyLoadBalancerConfig { @Resource private NacosDiscoveryProperties nacosDiscoveryProperties; //自定义loadBlancer负载均衡策略 @Bean public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); //返回随机轮询负载均衡方式 return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); //返回加权随机轮询负载均衡方式 //return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); //nacos服务注册中心权重的负载均衡策略 // return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name, nacosDiscoveryProperties); } } 启动类上配置服务使用的负载均衡策略package cc.lisen.shop.order; import cc.lisen.shop.order.config.MyLoadBalancerConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) @EnableDiscoveryClient @EnableFeignClients @LoadBalancerClients(value = @LoadBalancerClient(name = "service-product",configuration = MyLoadBalancerConfig.class)) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }此时,我们运行订单工程,访问接口http://127.0.0.1:8091/order/product/1多次刷新之后,可以用看到服务随机打到了8081和8082端口。
2022年11月24日
811 阅读
0 评论
0 点赞
2022-11-23
Spring Cloud Alibaba笔记修订版-第二章微服务环境搭建
因为第一章都是一些概念性的东西,包括系统架构的演变、微服务架构的介绍(服务调度、服务治理、服务容错、链路追踪等等),大家感兴趣的可以阅读原文,我们这里直接从第二章微服务环境搭建开始。本次使用的电商项目中的商品、订单、用户为案例进行讲解。一、技术选型JDK :1.8maven :3.8.6数据库 :MySQL 8.0.31持久层 :SpringData Jpa其他 :Spring Cloud Alibaba 2021.0.4.0,截止到目前最新版本开发工具 :IntelliJ idea 2022.2## 二、模块设计springcloud-alibaba:父工程shop-common:公共模块【实体类】shop-user:用户微服务【端口:807x】shop-product:商品微服务【端口:808x】`shop-order:订单微服务【端口:809x】2.1、创建父工程打开idea,创建maven工程,选择【New Project】,输入Name、GroupId、ArtifactId,选择存储目录,JDK选择本机安装的1.8版本。点击CREATE完成项目创建。springcloud-alibaba只是作为父工程,我们不会写任何代码,所以直接把src文件夹整体删掉。然后在pom.xml中添加相关依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.11</version> <relativePath/> </parent> <groupId>cc.lisen</groupId> <artifactId>springcloud-alibaba</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> <java.version>1.8</java.version> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version> <spring-cloud.version>2021.0.4</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <!-- Spring Cloud依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud Alibaba依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud.version}</version> </dependency> </dependencies> </dependencyManagement> </project>注意一下是Spring Cloud Alibaba与Spring Cloud及Spring Boot的版本对应关系,一定要选择对应的版本。Spring Cloud Alibaba VersionSpring Cloud VersionSpring Boot Version2021.0.4.0*Spring Cloud 2021.0.42.6.112021.0.1.0Spring Cloud 2021.0.12.6.32021.1Spring Cloud 2020.0.12.4.22.2、创建shop-common模块2.2.1、创建模块在工程上右键,选择NEW→Module名称输入shop-common创建模块2.2.2、添加依赖因为我们使用JPA,因此需要引入JPA的依赖,为了减少代码量,同时使用了lombok及fastjson序列化,所以shop-common添加依赖以下依赖 <dependencies> <!-- jpa依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- fastjson依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.19</version> </dependency> <!-- MySQL依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.31</version> </dependency> </dependencies>2.2.3、创建包目前shop-common模块中,src文件夹还是空的,为了规范,我们把shop-common的包定义为cc.lisen.shop.common2.2.4、创建实体我们统一把实体放到entity下,创建三个实体:用户(User)、商品(Product)、订单(Order)package cc.lisen.shop.common.entity; import lombok.Data; import javax.persistence.*; /** * 用户 */ @Data @Entity @Table(name = "shop_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer ID; /** * 用户名 */ private String name; /** * 密码 */ private String password; /** * 手机号码 */ private String telephone; } package cc.lisen.shop.common.entity; import lombok.Data; import javax.persistence.*; /** * 商品 */ @Data @Entity @Table(name = "shop_product") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer ID; /** * 商品名 */ private String name; /** * 价格 */ private Double price; /** * 库存 */ private Integer stock; } package cc.lisen.shop.common.entity; import lombok.Data; import javax.persistence.*; /** * 订单 */ @Data @Entity @Table(name = "shop_order") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer ID; /** * 用户ID */ private Integer uid; /** * 商品ID */ private Integer pid; } 2.3、创建用户微服务2.3.1、创建模块参考2.2.1创建用户模块shop-user2.3.2、添加依赖添加以下依赖即可。 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>cc.lisen</groupId> <artifactId>shop-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>2.3.3、创建包目前shop-user模块中,src文件夹还是空的,为了规范,我们把shop-user的包定义为cc.lisen.shop.user2.3.4、编写主类shop-user作为一个微服务,必须是可独立运行的,因此必须创建一个主类UserApplication.java。package cc.lisen.shop.product; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } }2.3.5、创建配置文件在resources文件夹添加application.yaml配置文件server: port: 8071 spring: application: name: service-user datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.236.2/shop?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=true username: root password: root jpa: hibernate: #指定为update,每次启动项目检测表结构有变化的时候会新增字段,表不存在时会新建,如果指定create,则每次启动项目都会清空数据并删除表,再新建 ddl-auto: update naming: #指定jpa的自动表生成策略,驼峰自动映射为下划线格式 implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl # 默认false,在日志里显示执行的sql语句 show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect database: mysql database-platform: org.hibernate.dialect.MySQL5Dialect2.3.6、验证设置完成后,shop-user整个结构如下启用UserApplication,此时控制台输入一下内容,说明启动成功2.4、创建商品微服务2.4.1、创建模块参考2.2.1创建商品模块shop-product2.4.2、添加依赖添加以下依赖即可。 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>cc.lisen</groupId> <artifactId>shop-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>2.4.3、创建包目前shop-product模块中,src文件夹还是空的,为了规范,我们把shop-product的包定义为cc.lisen.shop.product2.4.4、编写主类shop-product作为一个微服务,必须是可独立运行的,因此必须创建一个主类ProductApplication.java。package cc.lisen.shop.user; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); } }2.4.5、创建配置文件在resources文件夹添加application.yaml配置文件server: port: 8081 spring: application: name: service-product datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.236.2/shop?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=true username: root password: root jpa: hibernate: #指定为update,每次启动项目检测表结构有变化的时候会新增字段,表不存在时会新建,如果指定create,则每次启动项目都会清空数据并删除表,再新建 ddl-auto: update naming: #指定jpa的自动表生成策略,驼峰自动映射为下划线格式 implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl # 默认false,在日志里显示执行的sql语句 show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect database: mysql database-platform: org.hibernate.dialect.MySQL5Dialect2.4.6、验证设置完成后,shop-product整个结构如下启用ProductApplication,此时控制台输入一下内容,说明启动成功2.5、创建订单微服务2.5.1、创建模块参考2.2.1创建商品模块shop-order2.5.2、添加依赖添加以下依赖即可。 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>cc.lisen</groupId> <artifactId>shop-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>2.5.3、创建包目前shop-order模块中,src文件夹还是空的,为了规范,我们把shop-product的包定义为cc.lisen.shop.order2.5.4、编写主类shop-order作为一个微服务,必须是可独立运行的,因此必须创建一个主类OrderApplication.java。package cc.lisen.shop.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }2.5.5、创建配置文件在resources文件夹添加application.yaml配置文件server: port: 8091 spring: application: name: service-order datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.236.2/shop?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=true username: root password: root jpa: hibernate: #指定为update,每次启动项目检测表结构有变化的时候会新增字段,表不存在时会新建,如果指定create,则每次启动项目都会清空数据并删除表,再新建 ddl-auto: update naming: #指定jpa的自动表生成策略,驼峰自动映射为下划线格式 implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl # 默认false,在日志里显示执行的sql语句 show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect database: mysql database-platform: org.hibernate.dialect.MySQL5Dialect2.5.6、验证设置完成后,shop-order整个结构如下启用OrderApplication,此时控制台输入一下内容,说明启动成功三、原始服务调用3.1、创建测试数据为了方便后续演示,我们现在用户及商品表中创建几条测试数据。INSERT INTO shop_user (id, name, password, telephone) VALUES (1, '张三', '123456', '13333333333'); INSERT INTO shop_user (id, name, password, telephone) VALUES (2, '李四', '123456', '14444444444'); INSERT INTO shop_product (id, name, price, stock) VALUES (1, '小米', 1000, 5000); INSERT INTO shop_product (id, name, price, stock) VALUES (2, '华为', 2000, 5000); INSERT INTO shop_product (id, name, price, stock) VALUES (3, '苹果', 3000, 5000); INSERT INTO shop_product (id, name, price, stock) VALUES (4, '一加', 4000, 5000); 3.2、商品提供查询服务3.2.1、数据访问层创建商品数据访问层一个空的接口,JPA默认会提供查询方法package cc.lisen.shop.product.dao; import cc.lisen.shop.common.entity.Product; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; /** * 商品数据访问层 */ @Repository public interface ProductDao extends JpaRepository<Product, Integer> { } 3.2.2、服务层创建ProductService及接口实现类package cc.lisen.shop.product.service; import cc.lisen.shop.common.entity.Product; public interface ProductService { /** * 根据ID查询商品 * * @param ID 商品ID * @return 商品 */ Product findByID(Integer ID); } package cc.lisen.shop.product.service.impl; import cc.lisen.shop.common.entity.Product; import cc.lisen.shop.product.dao.ProductDao; import cc.lisen.shop.product.service.ProductService; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class ProductServiceImpl implements ProductService { @Resource private ProductDao productDao; /** * 根据ID查询商品 * * @param ID 商品ID * @return 商品 */ @Override public Product findByID(Integer ID) { return productDao.findById(ID).get(); } } 3.2.3、创建Rest接口package cc.lisen.shop.product.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import cc.lisen.shop.product.service.ProductService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j public class ProductController { @Resource private ProductService productService; @GetMapping("/product/{id}") public Product product(@PathVariable("id") Integer ID) { return productService.findByID(ID); } } 运行程序,现在浏览器测试一下3.3、订单查询商品3.3.1、创建配置文件我们这里通过RestTemplate访问商品,因此增加一个配置文件。package cc.lisen.shop.order.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class OrderConfiguration { @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } } 3.3.1、创建Rest接口这里简单模拟一下订单查询商品信息,只简单创建一个订单的controllerpackage cc.lisen.shop.order.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @RequestMapping("order") @Slf4j public class OrderController { @Resource private RestTemplate restTemplate; @GetMapping("product/{id}") public Product order(@PathVariable("id") Integer productID){ return restTemplate.getForObject("http://localhost:8081/product/1", Product.class); } } 简单测试一下订单接口接口也是能够正常访问的。四、传统服务调用的一些弊端通过上面的代码,我们虽然实现了接口的调用,但是我们把服务提供者的网络地址(ip、端口)等硬编码到了代码中,这种做法存在很多问题:一旦服务提供者地址发生变化,就需要手工修改代码(当然可以做成配置或者放到配置文件中)一旦是多个服务提供者,无法实现负载均衡功能一旦服务变得越来越多,人工维护调用关系变得非常困难为了解决上述问题,我们就引出了微服务架构中的服务治理,也就是通过注册中心等方式,实现服务的自动注册与发现。
2022年11月23日
938 阅读
0 评论
1 点赞
2022-11-22
Spring Cloud Alibaba笔记修订版-序言
一、为什么会有Spring Cloud Alibaba笔记修订版一系列的文章1、加强个人学习很无意间看到的“一本书”,之所以打上双引号,是一位这不是完整意义上的一本书,其实如标题说的一样,是一本笔记。【笔记】其实更像是个人学习的一个总结,所以书中内容可能会针对个人有深有浅,对于旁人来说,就是左一榔头、有一棒槌,云里雾里,不知所踪。为了个人的学习加深,也为了将别人的东西消化成自己的东西,因此在阅读这本书的时候,有意的做一下记录,形成一套完整的适合大多数人学习的Spring Cloud Alibaba笔记。2、完善代码书中错误【马虎】的代码比较多其实从一开始阅读,我就发发现笔记中有不少“错误”或者说是马虎的粘贴导致的错误,比如用户的微服务叫service-product,其实这是商品的微服务。笔记内代码相对比较古老,好多依赖都已经存在已知的漏洞Spring Cloud Alibaba笔记写的比较早,所以使用的Spring Cloud Alibaba还是比较早的版本,这并不是说是啥问题,只是随着时间的发展,新的版本替代老的版本是必然的趋势,因此我在阅读这个笔记的时候,特意验证了Spring Cloud Alibaba最新的版本,并且基于最新的版本进行代码的验证。二、Spring Cloud Alibaba笔记的改动点勘误其实就是对不合适或者马虎粘贴的代码进行一些改正。升级版本Spring Cloud Alibaba 升级到2021.0.1.0Spring Boot升级到2.6.3Spring Cloud升级到2021.0.1
2022年11月22日
975 阅读
0 评论
0 点赞
2022-11-19
油耗笔记OilNote集成腾讯地图实现载在图选择加油站充电站功能
油耗笔记OilNote经过两轮迭代,将地图服务由高德地图切换到百度地图,最终定格到了腾讯地图。在前面,我们已经实现了逆地址解析及周边搜索功能。接下来我们要实现打开地图,在图直接选择加油站功能。我们使用腾讯地图提供的微信小程序JavaScript SDK实现在线地图功能。一、微信小程序JavaScript SDK介绍腾讯位置服务为微信小程序提供了基础的标点能力、线和圆的绘制接口等地图组件和位置展示、地图选点等地图API位置服务能力支持,使得开发者可以自由地实现自己的微信小程序产品。 在此基础上,腾讯位置服务微信小程序JavaScript SDK是专为小程序开发者提供的LBS数据服务工具包,可以在小程序中调用腾讯位置服务的POI检索、关键词输入提示、地址解析、逆地址解析、行政区划和距离计算等数据服务,让您的小程序更强大!微信小程序JavaScript SDK开发文档二、微信小程序JavaScript SDK使用2.1、小程序域名设置在小程序管理后台 -> 开发 -> 开发管理 -> 开发设置 -> “服务器域名” 中设置request合法域名,添加https://apis.map.qq.com2.2、引入SDK下载微信小程序JavaScriptSDK,微信小程序JavaScriptSDK v1.1 JavaScriptSDK v1.2,并将解压后的.js文件引入工程中。2.3、页面引入2.3.1、在页面引入脚本文件 // 引入SDK核心类,js文件根据自己业务,位置可自行放置 var QQMapWX = require('../../common/utils/qqmap-wx-jssdk.min.js'); //引入下载好的sdk var qqmapsdk; // 实例化API核心类 qqmapsdk = new QQMapWX({ key: LocationUtils.TENCENT_MAP_KEY });2.3.2、查询加油站//页面加载 onLoad(options) { let that = this; this.latitude = uni.getStorageSync('latitude'); this.longitude = uni.getStorageSync('longitude'); this.oilType = options.oilType === '电' ? '充电站' : '加油站' let iconPath = 'https://oss.lisen.cc/oilnote/images/chargingPile.png'; if (options.oilType !== '电') { iconPath = 'https://oss.lisen.cc/oilnote/images/oilStation.png'; } // 调用接口 qqmapsdk.search({ keyword: encodeURI(that.oilType), location: { latitude: that.latitude, longitude: that.longitude }, page_size: 20, auto_extend: '1', success: function(res) { var selfLocation = { id: -1, latitude: that.latitude, longitude: that.longitude, width: 20, height: 20, iconPath: 'https://oss.lisen.cc/oilnote/images/location.png', title: "我的位置", callout: { content: '我的位置', color: '#adec9c', display: 'BYCLICK', borderRadius: 5, } }; that.makers.push(selfLocation); if (res.status === 0) { res.data.forEach((item, index) => { let obj = { id: index, latitude: item.location.lat, longitude: item.location.lng, width: 20, height: 20, iconPath: iconPath, title: item.title, callout: { content: item.title, bgColor: '#adec9c', display: 'ALWAYS', borderRadius: 5, } }; that.makers.push(obj); }) } }, fail: function(res) { console.log(res); }, }) }markers数组就是我们要标记的坐标点,数组中对象的属性id,表示标记点id,类型为Number,必填项,marker点击事件回调会返回此id,建议为每个marker设置上Number类型id,保证更新marker时有更好的性能。latitude,纬度,类型Number,必填项,浮点数,范围 -90 ~ 90longitude,经度,类型Number,必填项,浮点数,范围 -180 ~ 180title,标注点名,类型String,不是必填,点击时显示,callout存在时将被忽略iconPath,显示的图标,类型String,必填项,项目目录下的图片路径rotate,旋转角度,类型Number,不是必填,顺时针旋转的角度,范围 0 ~ 360,默认为 0alpha,标注的透明度,类型Number,不是必填,默认1,无透明,范围 0 ~ 1width,标注图标宽度,类型Number,不是必填,默认为图片实际宽度height,标注图标高度,类型Number,不是必填,默认为图片实际高度callout,自定义标记点上方的气泡窗口,类型Object,不是必填 - 可识别换行符label,为标记点旁边增加标签,类型Object,不是必填 - 可识别换行符anchor,经纬度在标注图标的锚点,默认底边中点,不是必填,{x, y},x表示横向(0-1),y表示竖向(0-1)。{x: .5, y: 1} 表示底边中点。marker上的气泡callout(Object类型), marker数组上属性callout对象使用属性:content,文本,Stringcolor,文本颜色,StringfontSize,文字大小,NumberborderRadius,callout边框圆角,NumberbgColor,背景色,Stringpadding,文本边缘留白,Numberdisplay,'BYCLICK':点击显示; 'ALWAYS':常显,StringtextAlign,文本对齐方式。有效值: left, right, center,Stringmarker上的标签label(Object类型)content,文本,Stringcolor,文本颜色,StringfontSize,文字大小,Numberx,label的坐标,原点是 marker 对应的经纬度,Numbery,label的坐标,原点是 marker 对应的经纬度,NumberborderWidth,边框宽度,NumberborderColor,边框颜色,StringborderRadius,边框圆角,NumberbgColor,背景色,Stringpadding,文本边缘留白,NumbertextAlign,文本对齐方式。有效值: left, right, center,String2.3.3、界面展示获取到数据之后,就是通过marker属性进行展示<map @tap="getMapLocation" :style="[{height:'calc(100vh - ' + CustomBar + 'px - 50px)'}]" style="width:100%" :latitude="latitude" :longitude="longitude" :markers="makers" @markertap="markerTap"> </map>2.3.3.1、地图组件的属性:longitude(类型为Number,没有默认值,表示中心经度)latitude(类型为Number,没有默认值,表示中心纬度)scale(类型为Number,默认值为16,缩放级别取值范围为5-18)markers(类型为Array数组,类型为数组即表示地图上可以有多个,没有默认值,表示标记点)polyline(类型为Array数组,没有默认值,表示路线,数组上的所有点连成线)circles(类型Array数组,表示圆)controls(类型Array数组,表示控件)include-points(类型Array数组,表示缩放视野已包含所有给定的坐标点)enable-3D(类型为Boolean,默认值为false,表示是否显示3D搂块)show-compass(类型为Boolean,默认值为false,表示为是否显示指南针)enable-overlooking(类型为Boolean,默认值为false,表示为是否开启俯视)enable-satellite(类型为Boolean,默认值为false,表示为是否开启卫星图)enable-traffic(类型为Boolean,默认值为false,表示为是否开启实时路况)show-location(类型为Boolean,表示显示带有方向的当前定位点)polygons(类型Array,表示为多边形)2.3.3.2、组件方法@markertap-表示点击标记点时触发,e.detail={markerId}@labeltap-表示点击label时触发,e.detail = {markerId}@callouttap-表示点击标记点对应的气泡时触发,e.detail = {markerId}@controltap-表示点击控件时触发,e.detail = {controlId}@regionchange-表示视野发生变化时触发@tap-表示点击地图时触发; App-nuve、微信小程序2.9支持返回经纬度@updated-表示在地图渲染更新完成时触发
2022年11月19日
1,163 阅读
0 评论
0 点赞
2022-11-18
油耗笔记OilNote周边搜索能力由百度地图切换成腾讯地图
油耗笔记OilNote其实对地图搜索能力,其实就是查询周边加油站的功能进行过一次升级,上次升级是将地图搜索能力由高德地图切换成了百度地图,切换原因也非常简单,高德地图无耻的将每日额度改成了100,根本没法用了。具体切换过程,感兴趣的可以看看。油耗笔记OilNote周边搜索能力由高德地图切换成百度地图 - 李森的博客 (lisen.cc)一、为什么又不用百度地图了1.1、对搜索结果不满意其实,从高德地图切换到百度地图也不过一天的时间。之所以不使用百度地图,最根本的原因,是百度地图所谓的圆形搜索能力真的太差劲了。好多东西根本就搜不到。而且作为记录油耗的一个小程序,搜索周边加油站其实是最基础最基本的一个功能,但是百度地图的圆形搜索能力简直就弱爆了。明明旁边就有加油站,死活就是搜索不到。但是百度地图自己却能搜索到。比如这个地图是当时使用百度地图的搜索加油站的结果下面这个是切换到腾讯地图在相同位置的搜索加油站的结果但是,百度地图搜索的充电站数量好像也不少,跟腾讯地图也有差不多。1.2、坐标转换比较麻烦使用uni.getLocation()方法虽然获取到的坐标是gcj02,但是在百度地图下,得使用百度的坐标,必须转换一次,不然坐标误差是非常大的。1.3、官方文档写的太乱百度地图的官方文档写的,真的是一言难尽。1.4、资料很少貌似很多人都用高德的地图API,网上一搜很多,但是百度地图的相对就比较少了。虽然腾讯地图网上资料比较少,但是官网的开发文档还是很清晰明了的,这在一定程度上弥补了网上资料少的缺点。二、腾讯地图额度信息腾讯地图也是比较良心的,每日额度是三家里面最多的10000次/天,是高德地图的100倍,百度地图的2倍。缺点就是并发比较少,只有5次/秒。对于我们现阶段也是很充足的了。三、微信公众平台配置如果使用腾讯地图,必须提前把腾讯地图Api放到域名白名单里面。微信小程序后台,依次定位到开发管理→开发设置→服务器域名,在request合法域名里面加上百度地图的网址https://apis.map.qq.com/如果微信开发者工具提示域名不合法,记得刷新一下开发者工具的域名信息。刷新后记得重新编译项目。四、获取附近加油站功能改造其实三家地图的API都是类似的,我这里主要用了两个API,逆地址解析geocoder还有地点搜索search4.1、逆地址解析用于将uniapp获取到的经纬度解析成实际位置。let getLocation = function(radius = 1000, successFn) { var latitude = '' //纬度 var longitude = '' //经度 if (uni.getStorageSync('position') == '') { uni.getLocation({ geocode: true, type: 'gcj02', altitude: true, accuracy: 'best', isHighAccuracy: true, success: (res) => { console.log('位置是', res) latitude = res.latitude longitude = res.longitude uni.setStorageSync('latitude', latitude) uni.setStorageSync('longitude', longitude) uni.request({ url: 'https://apis.map.qq.com/ws/geocoder/v1/?key=' + TENCENT_MAP_KEY + '&location=' + latitude + ',' + longitude + '&get_poi=1&output=json', success: function(res) { let result = res.data.result let province = result.address_component.province let city = result.address_component.city // 省份名称 uni.setStorageSync('province', province) // 城市名称 uni.setStorageSync('city', city) // 城市编号 uni.setStorageSync('citycode', result.ad_info.city_code) successFn(result, province, city) }, fail(err) { console.log('获取位置信息失败:' + JSON.stringify(err)) } }) }, fail: (err) => { console.log('获取位置信息失败:' + JSON.stringify(err)) } }) } }接口示例https://apis.map.qq.com/ws/geocoder/v1/?location=39.984154,116.307490&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&get_poi=1请求参数参数必填说明示例key是开发密钥(Key)key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-*location是经纬度(GCJ02坐标系),格式: location=lat<纬度>,lng<经度>location= 39.984154,116.307490get_poi否是否返回周边地点(POI)列表,可选值: 0 不返回(默认) 1 返回get_poi=1poi_options否周边POI列表控制参数: 1 poi_options=address_format=short 返回短地址,缺省时返回长地址 2 poi_options=radius=5000 半径,取值范围 1-5000(米) 3 poi_options=policy=1/2/3/4/5 控制返回场景, policy=1[默认] 以地标+主要的路+近距离POI为主,着力描述当前位置; policy=2 到家场景:筛选合适收货的POI,并会细化收货地址,精确到楼栋; policy=3 出行场景:过滤掉车辆不易到达的POI(如一些景区内POI),增加道路出入口、交叉口、大区域出入口类POI,排序会根据真实API大用户的用户点击自动优化。 policy=4 社交签到场景,针对用户签到的热门 地点进行优先排序。 policy=5 位置共享场景,用户经常用于发送位置、位置分享等场景的热门地点优先排序 4 注:policy=1/2/3最多返回10条周边POI,policy=4/5最多返回20条, 如需更多请参见地点搜索-周边推荐【单个参数写法示例】: poi_options=address_format=short 【多个参数英文分号间隔,写法示例】: poi_options=address_format=short;radius=5000;policy=2output否返回格式:支持JSON/JSONP,默认JSONoutput=jsoncallback否JSONP方式回调函数callback=function1响应结果名称类型必填说明 statusnumber是状态码,0为正常,其它为异常,详细请参阅状态码说明 messagestring是状态说明 request_idstring是本次请求的唯一标识 resultobject是逆地址解析结果 addressstring是以行政区划+道路+门牌号等信息组成的标准格式化地址formatted_addressesobject否结合知名地点形成的描述性地址,更具人性化特点 recommendstring否推荐使用的地址描述,描述精确性较高roughstring否粗略位置描述 address_componentobject是地址部件,address不满足需求时可自行拼接 nationstring是国家provincestring是省 citystring是市,如果当前城市为省直辖县级区划,city与district字段均会返回此城市 注:省直辖县级区划adcode第3和第4位分别为9、0,如济源市adcode为419001 districtstring否区,可能为空字串 streetstring否街道,可能为空字串 street_numberstring否门牌,可能为空字串 ad_infoobject是行政区划信息 nation_codestring是国家代码(ISO3166标准3位数字码) adcodestring是行政区划代码,规则详见:行政区划代码说明 city_codestring是城市代码,由国家码+行政区划代码(提出城市级别)组合而来,总共为9位namestring是行政区划名称 locationobject是行政区划中心点坐标 latnumber是纬度lngnumber是经度 nationstring是国家 provincestring是省 / 直辖市 citystring是市 / 地级区 及同级行政区划,如果当前城市为省直辖县级区划,city与district字段均会返回此城市 注:省直辖县级区划adcode第3和第4位分别为9、0,如济源市adcode为419001 districtstring否区 / 县级市 及同级行政区划 address_referenceobject否坐标相对位置参考 famous_areaobject否知名区域,如商圈或人们普遍认为有较高知名度的区域 idstring是地点唯一标识titlestring否名称/标题 locationobject否坐标 latnumber否纬度lngnumber否经度 _distancenumber否此参考位置到输入坐标的直线距离 _dir_descstring否此参考位置到输入坐标的方位关系,如:北、南、内 business_areaobject否商圈,目前与famous_area一致 townobject否乡镇街道 idstring是地点唯一标识titlestring否名称/标题 locationobject否坐标 latnumber否纬度lngnumber否经度 _distancenumber否此参考位置到输入坐标的直线距离 _dir_descstring否此参考位置到输入坐标的方位关系,如:北、南、内 landmark_l1object否一级地标,可识别性较强、规模较大的地点、小区等 【注】对象结构同 famous_area landmark_l2object否二级地标,较一级地标更为精确,规模更小 【注】:对象结构同 famous_area streetobject否街道 【注】:对象结构同 famous_area street_numberobject否门牌 【注】:对象结构同 famous_area crossroadobject否交叉路口 【注】:对象结构同 famous_area waterobject否水系 【注】:对象结构同 famous_area poi_countnumber 查询的周边poi的总数,仅在传入参数get_poi=1时返回 poisarray否周边地点(POI)数组,数组中每个子项为一个POI对象 idstring否地点(POI)唯一标识titlestring否名称 addressstring否地址 categorystring否地点分类信息 locationobject否提示所述位置坐标 latnumber否纬度lngnumber否经度 ad_infoobject否行政区划信息 adcodenumber是行政区划代码provincestring是省,如果当前城市为直辖市,返回空 citystring是市,如果当前城市为省直辖县级区划,city与district字段均会返回此城市 注:省直辖县级区划adcode第3和第4位分别为9、0,如济源市adcode为419001 districtstring是区 _distancenumber否该POI到逆地址解析传入的坐标的直线距离 4.2、获取附近加油站let getOilStation = function(stationType = '加油站', radius = 1000, successFn) { let latitude = uni.getStorageSync('latitude') //纬度 let longitude = uni.getStorageSync('longitude') //经度 uni.request({ url: 'https://apis.map.qq.com/ws/place/v1/search?key=' + TENCENT_MAP_KEY + '&boundary=nearby(' + latitude + ',' + longitude + ',' + radius + ',0)' + '&keyword=' + encodeURI(stationType) + '&page_size=20&page_index=1' + '&orderby=_distance&output=json', success: function(res) { successFn(res) } }) }请求URLhttps://apis.map.qq.com/ws/place/v1/search请求参数请求方式(Method): GET参数必填说明示例key是开发密钥(Key)key=OB4BZ-D4W3U-*keyword是搜索关键字,长度最大96个字节,注:keyword仅支持检索一个。 (API采用UTF-8字符编码,1个英文字符占用1个字节, 1个中文字符占3个字节,具体请参阅相关技术资料)keyword=酒店,注意键值要进行URL编码(推荐encodeURI),如 keyword=%e9%85%92%e5%ba%97boundary是格式: boundary=nearby(lat,lng,radius[, auto_extend]) 子参数: lat,lng:搜索中心点的经纬度,格式顺序为纬度在前,经度在后 radius:搜索半径,单位:米,取值范围:10到1000 auto_extend:[可选] 当前范围无结果时,是否自动扩大范围,取值: 0 不扩大 1 [默认] 自动扩大范围(依次按照按1公里、2公里、5公里, 最大到全城市范围搜索)boundary=nearby(28.681114,115.918377,1000,1)get_subpois否是否返回子地点,如大厦停车场、出入口等取值: 0 [默认]不返回 1 返回get_subpois=1filter否筛选条件 1. 指定分类筛选,语句格式为: category=分类名1,分类名2 最多支持5个分类词(支持的分类请参考:POI分类表) 2. 排除指定分类,语句格式为: category<>分类名1,分类名2 最多支持5个分类词(支持的分类请参考:POI分类表) 3. 筛选有电话的地点:tel<>null搜索指定分类 filter=category=公交站 搜索多个分类 filter=category=大学,中学 排除指定分类 filter=category<>商务楼宇 (注意参数值要进行url编码)orderby否排序,支持按距离由近到远排序,取值:_distance 说明: 1. 周边搜索默认排序会综合考虑距离、权重等多方面因素 2. 设置按距离排序后则仅考虑距离远近,一些低权重的地点可能因距离近排在前面,导致体验下降orderby=_distancepage_size否每页条目数,最大限制为20条,默认为10条page_size=10page_index否第x页,默认第1页page_index=2output否返回格式: 支持JSON/JSONP,默认JSONoutput=jsoncallback否JSONP方式回调函数callback=function1响应结果名称类型必填说明 statusnumber是状态码,0为正常,其它为异常,详细请参阅状态码说明 messagestring是状态说明 countnumber是本次搜索结果总数,另外本服务限制最多返回200条数据(data), 翻页(page_index)超过搜索结果总数 或 最大200条限制时,将返回最后一页数据。 request_idstring是本次请求的唯一标识,由系统自动生成,用于追查结果有异常时使用 dataarray是搜索结果POI(地点)数组,每项为一个POI(地点)对象 idstring是POI(地点)唯一标识 titlestring是POI(地点)名称 addressstring是地址 telstring是电话 categorystring是POI(地点)分类 typenumber是POI类型,值说明:0:普通POI / 1:公交车站 / 2:地铁站 / 3:公交线路 / 4:行政区划 locationobject是坐标 latnumber是纬度 lngnumber是经度 _distancenumber是距离,单位: 米,在周边搜索、城市范围搜索传入定位点时返回 ad_infoobject是行政区划信息 adcodenumber是行政区划代码,详见:行政区划代码说明 provincestring是省 citystring是市,如果当前城市为省直辖县级区划,此字段会返回为空,由district字段返回。 注:省直辖县级区划adcode第3和第4位分别为9、0,如济源市adcode为419001 districtstring是区sub_poisarray否子地点列表,仅在输入参数get_subpois=1时返回 parent_idstring是主地点ID,对应data中的地点IDidstring是地点唯一标识 titlestring是地点名称 telstring是电话 categorystring是POI(地点)分类 typenumber是POI类型,值说明:0:普通POI / 1:公交车站 / 2:地铁站 / 3:公交线路 / 4:行政区划 addressstring是地址 locationobject是坐标 latnumber是纬度lngnumber是经度 ad_infoobject是行政区划信息 adcodenumber是行政区划代码,详见:行政区划代码说明 provincestring是省 citystring是市,如果当前城市为省直辖县级区划,此字段会返回为空,由district字段返回。 注:省直辖县级区划adcode第3和第4位分别为9、0,如济源市adcode为419001 districtstring是区五、使用腾讯地图一些问题目前切换到腾讯地图之后遇到的一些问题不能显示加油站或充电站图片,百度地图是有个参数photo_show,如果photo_show开启,会提示授权失败。具体原因不清楚,没搜到,但是腾讯地图是直接就没有这个参数。并发确实少点。
2022年11月18日
1,139 阅读
0 评论
23 点赞
2022-11-17
油耗笔记OilNote周边搜索能力由高德地图切换成百度地图
一、交代下背景以前油耗笔记OilNote检索周边加油站功能,都是用的高德地图,起初,也不知道高德Api限制多少访问量,反正一直也没提示过超额,但是今天,莫名的收到了两条超额的短信。一开始收到80%超额的时候也没太当回事,觉得可能这个月改版,调试用的数量比较大,但是紧接着就收到了超额100%的提示,就感觉到不太对劲了,于是登录了高德后台,查询了一下配额。结果真是乖了个乖,搜索服务每日限额100次,这简直就是坑了,100次跟没有没啥区别,高德这是直接劝退个人开发者的节奏。查了下站内信,2022年10月26日发的站内信(没短信),通知2022年10月27日调整限额,连给你调整的时间都不给。坑你没商量呀。二、百度地图为什么选择百度地图呢?介个,国内好像除了高德就是百度了,不用也没办法。因为我就用到了地点检索,百度地图目前还算比较良心的,个人开发者每日限额是5000次。是高德地图的50倍。并发限制都是一样的30QPS。百度地图创建应用的时候,记得选择微信小程序,否则逆地理编码是不能用的。三、微信公众平台配置如果使用百度地图,必须提前把百度地图Api放到域名白名单里面。微信小程序后台,依次定位到开发管理→开发设置→服务器域名,在request合法域名里面加上百度地图的网址https://api.map.baidu.com如果微信开发者工具提示域名不合法,记得刷新一下开发者工具的域名信息。刷新后记得重新编译项目。三、功能改造既然决定了用百度地图,那么剩下的就是如何对程序进行改造了。因为我的小程序是使用uniapp开发的,所以这里就介绍一下uniapp的整个的改造过程。为了不影响在线版本,暂时先不删除高德域名。3.1、manifest.json改造manifest.json用于配置地图的Key,因为我之前使用的是高德的地图,切换到百度后,需要将相关配置改成百度地图的。其实我现在只是用了微信小程序,AK我直接在代码里面写死了,这里配置的appkey我感觉没啥用,对uniapp理解不是很深入,这块可能更多的是给App用的吧。我们Maps需要勾选百度地图并取消高德地图。具体的key,可以在百度开放平台,个人创建的应用中找到。3.2、获取附近加油站功能改造其实改造也比较简单,高德跟百度Api还是比较类似的。3.2.1、原来高德获取周边加油站的代码let getLocation = function(radius = 1000, successFn) { var latitude = '' //纬度 var longitude = '' //经度 if (uni.getStorageSync('position') == '') { uni.getLocation({ geocode: true, type: 'gcj02', altitude: true, accuracy: 'best', isHighAccuracy: true, success: (res) => { console.log('位置是', res) latitude = res.latitude longitude = res.longitude uni.setStorageSync('latitude', latitude) uni.setStorageSync('longitude', longitude) uni.request({ url: 'https://restapi.amap.com/v3/geocode/regeo?key=AK' + '&location=' + longitude + ',' + latitude + '&poitype=010100&radius=' + radius + '&extensions=all&batch=false&roadlevel=0', success: function(res) { var regeocode = res.data.regeocode // 省份名称 uni.setStorageSync('province', regeocode.addressComponent.province) // 城市名称 uni.setStorageSync('city', regeocode.addressComponent.city) // 城市编号 uni.setStorageSync('citycode', regeocode.addressComponent.citycode) successFn(regeocode) }, fail(err) { console.log('获取加油站信息失败:' + JSON.stringify(err)) } }) }, fail: (err) => { console.log('获取加油站信息失败:' + JSON.stringify(err)) } }) } }3.2.2、百度地图获取逆地址编码(根据坐标获取位置)let getLocation = function(radius = 1000, successFn) { var latitude = '' //纬度 var longitude = '' //经度 uni.getLocation({ geocode: true, type: 'gcj02', altitude: true, accuracy: 'best', isHighAccuracy: true, success: (res) => { console.log('位置是', res) latitude = res.latitude longitude = res.longitude uni.request({ url: 'https://api.map.baidu.com/geoconv/v1/?' + 'ak=您的AK(类型是服务端的)' + '&coords=' + longitude + ',' + latitude + '&from=3&to=5&output=json', success: function(res) { debugger if (res.data.status === 0) { longitude = res.data.result[0].x latitude = res.data.result[0].y uni.setStorageSync('latitude', latitude) uni.setStorageSync('longitude', longitude) uni.request({ url: 'https://api.map.baidu.com/reverse_geocoding/v3/?' + 'ak=您的AK(类型是小程序的)&output=json' + '&coord_type=gcj02ll' + '&location=' + latitude + ',' + longitude + '&radius=' + radius, success: function(res) { if (res.data.status === 0) { // 省份名称 uni.setStorageSync('province', res.data .result.addressComponent .province) // 城市名称 uni.setStorageSync('city', res.data .result.addressComponent .city) // 城市编号 uni.setStorageSync('citycode', res.data .result.cityCode) successFn(res.data, res.data.result .addressComponent.province, res.data.result .addressComponent.city) } }, fail(err) { console.log('获取位置信息失败:' + JSON.stringify(err)) } }) } }, fail(err) { console.log('获取位置信息失败:' + JSON.stringify(err)) } }) }, fail: (err) => { console.log('获取位置信息失败:' + JSON.stringify(err)) } }) }需要注意,uni.getLocation获取gcj02时的坐标适用于高德等地图,但是不适用百度地图,需要调用百度地图坐标转换接口geoconv转换成百度地图的坐标,不然会有较大的误差,同时地图转换接口使用的AK需要是服务端类型的应用的AK,也就是说我们实际上使用了两种AK,一种是服务端的,一种是小程序的。3.2.3、百度地图获取附近加油站let getOilStation = function(radius = 1000, successFn) { let latitude = uni.getStorageSync('latitude') //纬度 let longitude = uni.getStorageSync('longitude') //经度 uni.request({ url: 'https://api.map.baidu.com/place/v2/search?' + 'query=充电站$加油站&ak=您的AK(类型是小程序的)&output=json' + '&scope=2' + '&coord_type=2' + '&page_size=20' + '&location=' + latitude + ',' + longitude + '&radius=' + radius, success: function(res) { successFn(res) } }) }改造完成后验证一下四、使用百度地图一些问题目前切换到百度地图之后遇到的一些问题搜索功能显示不全,我家附近其实就有一个中国石化,但是百度地图搜索不到,充电站搜索还算比较全的不能显示图片,如果photo_show开启,会提示授权失败。
2022年11月17日
1,112 阅读
0 评论
0 点赞
2022-11-15
解决小程序报错the api need to be declared in the requiredPrivateInfos field in app.json
一、报错信息从报错信息可以大概看到,应该是缺少授权导致的。PS:这里真的想吐槽一下微信的接口,真的是三天两头的变,发个版就得调整一次,心累。二、微信官方文档说明打开微信开发文档查看配置信息。为了开发者能够正常使用获取模糊地理位置等接口,以及后续对于代码提审环节的优化,自 **2022 年 7 月 14 日起,开发者在使用地理位置相关接口时(共计 8 个),需要提前在 app.json 中进行配置。在代码中使用的地理位置相关接口(共计 8 个),开发者均需要在 app.json 中 requiredPrivateInfos 配置项中声明,代码格式如下:地理位置接口新增与相关流程调整 | 微信开放社区三、解决方法因为我是用uni-app框架开发的,所以是在manifest.json里面配置,如果是用其他框架开发的话,一般都是在app.json里面配置,不过原理都是一样的。
2022年11月15日
1,861 阅读
0 评论
7 点赞
2022-11-14
微信小程序获取用户头像后上传到七牛云
一、事情起因【油耗笔记OilNote】小程序好久没有升级了,最近打算对代码进行一些优化,但是新版本突然发现无法获取到用户微信头像及微信昵称了。查阅官方文档才知道,官方有对getUserProfile接口进行调整了。自 2022 年 10 月 25 日 24 时后(以下统称 “生效期” ),用户头像昵称获取规则将进行如下调整:自生效期起,小程序 wx.getUserProfile 接口将被收回:生效期后发布的小程序新版本,通过 wx.getUserProfile 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的小程序版本不受影响,但如果要进行版本更新则需要进行适配。自生效期起,插件通过 wx.getUserInfo 接口获取用户昵称头像将被收回:生效期后发布的插件新版本,通过 wx.getUserInfo 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的插件版本不受影响,但如果要进行版本更新则需要进行适配。通过 wx.login 与 wx.getUserInfo 接口获取 openId、unionId 能力不受影响。「头像昵称填写能力」支持获取用户头像昵称:如业务需获取用户头像昵称,可以使用「头像昵称填写能力」(基础库 2.21.2 版本开始支持,覆盖iOS与安卓微信 8.0.16 以上版本),具体实践可见下方《最佳实践》。小程序 wx.getUserProfile 与插件 wx.getUserInfo 接口兼容基础库 2.27.1 以下版本的头像昵称获取需求:对于来自低版本的基础库与微信客户端的访问,小程序通过 wx.getUserProfile 接口将正常返回用户头像昵称,插件通过 wx.getUserInfo 接口将正常返回用户头像昵称,开发者可继续使用以上能力做向下兼容。现在只要是发布的新版本,默认都需要调整,不然就显示下面灰色头像,已经发布的版本不受影响。既然官方调整了,那么我们也只有被动接受的份。二、油耗笔记的开发框架油耗笔记OilNote不是直接使用微信开发者工具开发的,而是使用UniApp开发的,后端是SpringBoot。三、改进思路3.1、获取用户头像由于官方指导意见是,使用button组件 open-type 的值设置为 chooseAvatar,当用户选择需要使用的头像之后,可以通过 bindchooseavatar 事件回调获取到头像信息的临时路径。从官方的指导我们可以看到,微信并没有给我们返回一个具体的路径,只是返回了一个临时的路径,因此我们就必须自己获取到这个临时的文件,然后存储起来。3.2、使用七牛云由于使用的腾讯云的低配服务器,带宽、存储都比较捉襟见肘,所以我打算把头像都存储到七牛云上,既能减轻带宽压力也能节省服务器空间。四、具体改进4.1、UniApp页面改进当用户通过微信登录时,此时获取用户头像信息,如果头像存在,登录之后跳转到首页,否则跳转到个人信息界面,让用户维护头像及昵称,此方法适用于新用户,同时也适用于老用户重新登录。//微信授权登录 getUserInfo(e) { let that = this; var p = this.getSetting(); p.then(function(isAuth) { console.log('是否已经授权', isAuth); if (isAuth) { console.log('用户信息,加密数据', e); //eData 包括//微信头像//微信名称 还有加密的数据. // let eData = JSON.parse(e.detail.rawData); uni.getUserProfile({ desc: 'Wexin', // 这个参数是必须的 success: function(infoRes) { //接下来就是访问接口. that.$request( 'wechat/authCode2Session?code=' + that.weChatCode, 'POST' ).then(function(res) { if (res.code == 200) { //将接口返回的数据保存在全局变量中. let userInfo = {} // 用户id userInfo.id = res.data.id userInfo.username = res.data.username userInfo.tel = res.data.tel userInfo.email = res.data.email userInfo.wechatOpenId = res.data.wechatOpenId userInfo.nickName = res.data.nickName userInfo.avatarUrl = res.data.avatarUrl ? res.data .avatarUrl : infoRes.userInfo.avatarUrl userInfo.gender = infoRes.userInfo.gender userInfo.password = '' if (!userInfo.province) { userInfo.province = uni.getStorageSync('province') } if (!userInfo.city) { userInfo.city = uni.getStorageSync('city') } uni.setStorageSync('userInfo', userInfo); if (!res.data.avatarUrl) { //没有头像时,跳转到用户信息维护界面 uni.redirectTo({ url: '/pages/profile/profile' }) } else { uni.redirectTo({ url: '/pages/index/index' }) } } }, function(err) { uni.showToast({ title: '授权登录失败!', mask: true, icon: 'none' }) } ) } }); } else { uni.showToast({ title: '授权失败,请确认授权已开启', mask: true, icon: 'none' }) } }); },4.1.1、登录界面改造4.1.2、个人信息界面改造油耗笔记之前有一个【个人信息】页面,因此我打算把修改头像的功能发放到里面。在合适的位置放入选择头像的按钮<button class="avatar-wrapper" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"> <view class="cu-avatar xl round margin-center" :style="{backgroundImage:'url('+userInfo.avatarUrl+')'}"></view> </button>增加回调方法用户选择微信头像之后,会回调chooseavatar方法,因此我们增加一个chooseavatar用于用户选择头像之后上传到服务器(进一步上传到七牛)//选择头像回调 onChooseAvatar(e) { const that = this; this.$set(this.userInfo, "avatarUrl", e.detail.avatarUrl); uni.uploadFile({ url: operate.api + 'user/uploadAvatar/', //上传接口 header: { token: that.userInfo.id ? that.userInfo.id : '', }, formData: { 'userInfo': JSON.stringify(that.userInfo) }, filePath: e.detail.avatarUrl, name: 'file', success: (uploadFileRes) => { uni.hideLoading(); const back = JSON.parse(uploadFileRes.data); if (back.code == 200) { that.$set(that.userInfo, 'avatarUrl', back.data.avatarUrl) } else { uni.showToast(back.msg) } }, fail: (error) => { uni.hideLoading(); uni.showToast("图片上传失败,请联系开发!") }, complete: function() { uni.hideLoading(); } }); }4.2、后端改造我们首先在后端增加一个方法,用于接受前端传递的附件及其他参数(我这里主要传递的是用户信息,用户更新用户表,记录头像地址)。4.2.1、Controller增加接受用户上传头像的Api,具体的实现我们稍后在说。/** * 上传头像 * * @param multipartFile 文件信息 * @return 用户信息 */ @PostMapping("uploadAvatar") public AjaxResult uploadAvatar(@RequestParam("file") MultipartFile multipartFile,@RequestParam("userInfo") String oilUser) { return AjaxResult.success(oilUserService.uploadAvatar(multipartFile)); }4.2.2、增加七牛云依赖经过上面改造,我们已经可以将用户头像上传到我们的后端了,接下来的任务就是将头像上传到我们的七牛云了。现在pom.xml中增加七牛云的依赖<!-- 七牛云--> <dependency> <groupId>com.qiniu</groupId> <artifactId>qiniu-java-sdk</artifactId> <version>7.2.28</version> </dependency>4.2.3、增加七牛云配置为了方便使用,我们将七牛云的一些配置信息放入yaml文件中,方便维护。# ========================== ↓↓↓↓↓↓ 七牛云配置 ↓↓↓↓↓↓ ========================== qiniu: accessKey: XXX # Key secretKey: XXX # 密钥 bucket: XXX # 空间名称 domain: XXX # 访问域名 dir: XXX/ # 目录参数说明:accessKey:AK,在七牛云,个人中心,密钥管理中可以看到secretKey:SK,在七牛云,个人中心,密钥管理中可以看到bucket:空间名称,根据自己创建的空间填写domain:访问域名,根据控件绑定的域名实际填写dir:存储路径,因为七牛云默认是直接存储到根目录,为了方便管理,我们可以创建子目录,比如avatar,可以填写avatar/4.2.4、增加配置类为了方便使用,我们将yaml的值,映射到配置类上。/** * 七牛云实体 */ @Component @ConfigurationProperties(prefix = "qiniu") public class QiNiuConfig { /** * Key */ private static String accessKey; /** * 密钥 */ private static String secretKey; /** * 空间名称 */ private static String bucket; /** * 访问域名 */ private static String domain; /** * 目录 */ private static String dir; public static String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { QiNiuConfig.accessKey = accessKey; } public static String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { QiNiuConfig.secretKey = secretKey; } public static String getBucket() { return bucket; } public void setBucket(String bucket) { QiNiuConfig.bucket = bucket; } public static String getDomain() { return domain; } public void setDomain(String domain) { QiNiuConfig.domain = domain; } public static String getDir() { return dir; } public void setDir(String dir) { QiNiuConfig.dir = dir; } } 4.2.5、封装公共方法为了方便调用,我们将上传、删除等方法封装到单独的服务中。接口/** * 七牛接口 */ public interface IQiNiuService { /** * 以文件的形式上传 * * @param file * @param fileName: * @return: java.lang.String */ String uploadFile(File file, String fileName) throws QiniuException; /** * 以流的形式上传 * * @param inputStream * @param fileName: * @return: java.lang.String */ String uploadFile(InputStream inputStream, String fileName) throws QiniuException; /** * 删除文件 * * @param key: * @return: java.lang.String */ String delete(String key) throws QiniuException; } 实现@Service public class QiNiuServiceImpl implements IQiNiuService, InitializingBean { // 七牛文件上传管理器 private final Configuration cfg; private final Auth auth; public QiNiuServiceImpl() { // //构造一个带指定 Region 对象的配置类 cfg = new Configuration(Region.huadong()); auth = Auth.create(QiNiuConfig.getAccessKey(), QiNiuConfig.getSecretKey()); } /** * 定义七牛云上传的相关策略 */ private StringMap putPolicy; @Override public String uploadFile(File file, String fileName) throws QiniuException { if (!StringUtils.isEmpty(QiNiuConfig.getDir())) { fileName = QiNiuConfig.getDir() + fileName; } UploadManager uploadManager = new UploadManager(cfg); Response response = uploadManager.put(file, fileName, getUploadToken()); int retry = 0; while (response.needRetry() && retry < 3) { response = uploadManager.put(file, fileName, getUploadToken()); retry++; } if (response.statusCode == 200) { return "http://" + QiNiuConfig.getDomain() + "/" + fileName; } return "上传失败!"; } @Override public String uploadFile(InputStream inputStream, String fileName) throws QiniuException { if (!StringUtils.isEmpty(QiNiuConfig.getDir())) { fileName = QiNiuConfig.getDir() + fileName; } UploadManager uploadManager = new UploadManager(cfg); Response response = uploadManager.put(inputStream, fileName, getUploadToken(), null, null); int retry = 0; while (response.needRetry() && retry < 3) { response = uploadManager.put(inputStream, fileName, getUploadToken(), null, null); retry++; } if (response.statusCode == 200) { return "http://" + QiNiuConfig.getDomain() + "/" + fileName; } return "上传失败!"; } @Override public String delete(String key) throws QiniuException { BucketManager bucketManager = new BucketManager(auth, cfg); Response response = bucketManager.delete(QiNiuConfig.getBucket(), key); int retry = 0; while (response.needRetry() && retry++ < 3) { response = bucketManager.delete(QiNiuConfig.getBucket(), key); } return response.statusCode == 200 ? "删除成功!" : "删除失败!"; } @Override public void afterPropertiesSet() throws Exception { this.putPolicy = new StringMap(); putPolicy.put("insertOnly", 0); } /** * 获取上传凭证 */ private String getUploadToken() { return this.auth.uploadToken(QiNiuConfig.getBucket(), null, 3600, putPolicy); } } 有几个需要注意的点:在构造函数中,构造Configuration时,需要指定区域,因为我是华东区域的,因此使用的是Region.huadong(),如果使用的其他区域的,需要根据自己实际区域指定。在指定策略时,因为我一个用户只允许一个头像,因此上传时,如果存在我们直接覆盖的,所以在afterPropertiesSet方法中,设置上传策略时,直接指定的putPolicy.put("insertOnly", 0);,即如果存在就覆盖,如果不想覆盖,可以设置putPolicy.put("insertOnly", 1);,但是此时需要注意,如果上传重名文件,会返回异常。4.2.6、完善后用户上传头像方法用户上传头像后,更新用户实体(但是此时不更新数据库),将更新后的实体返回到前端,点击保存时,再更新数据库。 @Override public OilUser uploadAvatar(MultipartFile multipartFile, OilUser oilUser) throws IOException { String originalFilename = multipartFile.getOriginalFilename(); if (originalFilename == null || !originalFilename.contains(".")) { throw new CustomException("文件名不正确"); } String fileName = "avatar" + oilUser.getId() + originalFilename.substring(originalFilename.lastIndexOf(".")); String avatarUrl = qiNiuService.uploadFile(multipartFile.getInputStream(), fileName); oilUser.setAvatarUrl(avatarUrl); // LambdaUpdateWrapper<OilUser> userUpdateWrapper = new LambdaUpdateWrapper<>(); // userUpdateWrapper.set(OilUser::getAvatarUrl, oilUser.getAvatarUrl()); // userUpdateWrapper.eq(OilUser::getId, oilUser.getId()); // oilUserMapper.update(null, userUpdateWrapper); return oilUser; }4.2.7、用户保存方法分改造用户保存方法主要增加userUpdateWrapper.set(OilUser::getAvatarUrl, user.getAvatarUrl());,当用户有头像时,同步更新用户的头像信息。/** * 新增或保存用户 * * @param user 用户 * @return 结果 */ public OilUser saveUser(OilUser user) { if (user == null) { throw new CustomException("用户信息不能为空"); } if (user.getId() == null) { user.setId(""); } if (checkUserNameExist(user)) { throw new CustomException("用户名已存在"); } if (StringUtils.isEmpty(user.getId())) { user.setId(UUID.randomUUID().toString()); if (!StringUtils.isEmpty(user.getPassword())) { user.setPassword(passwordEncoder.encode(user.getPassword())); } oilUserMapper.insert(user); } else { LambdaUpdateWrapper<OilUser> userUpdateWrapper = new LambdaUpdateWrapper<>(); userUpdateWrapper.set(OilUser::getUsername, user.getUsername()); userUpdateWrapper.set(OilUser::getNickName, user.getNickName()); userUpdateWrapper.set(OilUser::getTel, user.getTel()); userUpdateWrapper.set(OilUser::getEmail, user.getEmail()); if (!StringUtils.isEmpty(user.getPassword())) { userUpdateWrapper.set(OilUser::getPassword, passwordEncoder.encode(user.getPassword())); } if (!StringUtils.isEmpty(user.getProvince())) { userUpdateWrapper.set(OilUser::getProvince, user.getProvince()); } if (!StringUtils.isEmpty(user.getCity())) { userUpdateWrapper.set(OilUser::getCity, user.getCity()); } if (!StringUtils.isEmpty(user.getAvatarUrl())) { userUpdateWrapper.set(OilUser::getAvatarUrl, user.getAvatarUrl()); } userUpdateWrapper.eq(OilUser::getId, user.getId()); oilUserMapper.update(null, userUpdateWrapper); user.setPassword(""); } return user; }五、效果微信用户登录后,如果没有上传过头像,会自动跳转到【个人信息】页面在个人信息上传头像后,自动跳转到首页。六、其他注意事项七牛云域名需要配置HTTPS小程序域名白名单uploadFile合法域名需要配置后台上传附件的域名。
2022年11月14日
2,022 阅读
0 评论
3 点赞
2022-11-13
JDK 8(Java SE Development Kit)全平台全版本安装包免费下载
Java虽然都更新到19了,但是我们日常使用比较多的还是Java8,虽然Spring Boot 3最低依赖都是Java17了,但是Java8的群体依然十分庞大。Java8(又称JDK 1.8)是Oracle于2014年3月19日发布正式版,是一个重要的长期支持版本(LTS),在生产环境中得到了普遍的运用;Java8 相比之前版本,新增了如下功能Lambda 表达式;使用函数式编程,使代码更少,更简洁;Data Time API 改进,优化对时间的处理新增 Stream API新增 Optional 类提供新的 Nashorn JavaScript 引擎,允许在 JVM 上运行特定的JS应用默认方法方法参数反射Oracle JDK 8u311下载安装平台下载地址密码Windows x64 64位jdk-8u311-windows-x648899Windows x86 32位jdk-8u311-windows-i5868899Linux x64 64位jdk-8u311-linux-x648899Linux x86 32位jdk-8u311-linux-i5868899Linux aarch 64位jdk-8u311-linux-aarch648899Linux arm 32位jdk-8u311-linux-arm328899MacOS x64 64位jdk-8u311-macosx-x648899Solarisjdk-8u311-solaris8899Oracle JDK 8u301下载安装平台下载地址下载密码Windows x64 64位jdk-8u301-windows-x648899Windows x86 32位jdk-8u301-windows-i5868899Linux x64 64位jdk-8u301-linux-x648899Linux x86 32位jdk-8u301-linux-i5868899Linux aarch64 64位jdk-8u301-linux-aarch648899Linux arm 32位jdk-8u301-linux-arm328899MacOS x64 64位jdk-8u301-macosx-x648899Solarisjdk-8u301-solaris8899Oracle JDK 8u291下载安装平台下载地址下载密码Windows x64 64位jdk-8u291-windows-x648899Windows x86 32位jdk-8u291-windows-i5868899Linux x64 64位jdk-8u291-linux-x648899Linux x86 32位jdk-8u291-linux-i5868899Linux aarch64 64位jdk-8u291-linux-aarch648899Linux arm 32位jdk-8u291-linux-arm328899MacOS x64 64位jdk-8u291-macosx8899Solarisjdk-8u291-solaris8899Oracle JDK 8u281下载安装平台下载地址密码Windows x64 64位jdk-8u281-windows-x648899Windows x86 32位jdk-8u281-windows-i5868899Linux x64 64位jdk-8u281-linux-x648899Linux x86 32位jdk-8u281-linux-i5868899Linux aarch64 64位jdk-8u281-linux-aarch648899Linux arm 32位jdk-8u281-linux-arm328899MacOS x64 64位jdk-8u281-macosx-x648899Solarisjdk-8u281-solaris8899Oracle JDK 8u271下载安装平台下载地址密码Windows x64 64位jdk-8u271-windows-x648899Windows x86 32位jdk-8u271-windows-i5868899Linux x64 64位jdk-8u271-linux-x648899Linux x86 32位jdk-8u271-linux-i5868899Linux aarch64 64位jdk-8u271-linux-aarch648899Linux arm 32位jdk-8u271-linux-arm328899MacOS x64 64位jdk-8u271-macosx-x648899Solarisjdk-8u271-solaris8899Oracle JDK 8u261下载安装平台下载地址密码Windows x64 64位jdk-8u261-windows-x648899Windows x86 32位jdk-8u261-windows-i5868899Linux x64 64位jdk-8u261-linux-x648899Linux x86 32位jdk-8u261-linux-i5868899Linux arm 64位jdk-8u261-linux-arm648899Linux arm 32位jdk-8u261-linux-arm328899MacOS x64 64位jdk-8u261-macosx-x648899Solarisjdk-8u261-solaris8899Oracle JDK 8u251下载安装平台下载地址密码Windows x64 64位jdk-8u251-windows-x648899Windows x86 32位jdk-8u251-windows-i5868899Linux x64 64位jdk-8u251-linux-x648899Linux x86 32位jdk-8u251-linux-i5868899Linux arm 64位jdk-8u251-linux-arm648899Linux arm 32位jdk-8u251-linux-arm328899MacOS x64 64位jdk-8u251-macosx-x648899Solarisjdk-8u251-solaris8899Oracle JDK 8u241下载安装平台下载地址密码Windows x64 64位jdk-8u241-windows-x648899Windows x86 32位jdk-8u241-windows-i5868899Linux x64 64位jdk-8u241-linux-x648899Linux x86 32位jdk-8u241-linux-i5868899Linux arm 64位jdk-8u241-linux-arm648899Linux arm 32位jdk-8u241-linux-arm328899MacOS x64 64位jdk-8u241-macosx-x648899Solarisjdk-8u241-solaris8899Oracle JDK 8u231下载安装平台下载地址密码Windows x64 64位jdk-8u231-windows-x648899Windows x86 32位jdk-8u231-windows-i5868899Linux x64 64位jdk-8u231-linux-x648899Linux x86 32位jdk-8u231-linux-i5868899Linux arm 64位jdk-8u231-linux-arm648899Linux arm 32位jdk-8u231-linux-arm328899MacOS x64 64位jdk-8u231-macosx-x648899Solarisjdk-8u231-solaris8899Oracle JDK 8u221下载安装平台下载地址密码Windows x64 64位jdk-8u221-windows-x648899Windows x86 32位jdk-8u221-windows-i5868899Linux x64 64位jdk-8u221-linux-x648899Linux x86 32位jdk-8u221-linux-i5868899Linux arm 64位jdk-8u221-linux-arm648899Linux arm 32位jdk-8u221-linux-arm328899MacOS x64 64位jdk-8u221-macosx-x648899Solarisjdk-8u221-solaris8899Oracle JDK 8u212下载安装平台下载地址密码Windows x64 64位jdk-8u212-windows-x648899Windows x86 32位jdk-8u212-windows-i5868899Linux x64 64位jdk-8u212-linux-x648899Linux x86 32位jdk-8u212-linux-i5868899Linux arm 64位jdk-8u212-linux-arm648899Linux arm 32位jdk-8u212-linux-arm328899MacOS x64 64位jdk-8u212-macosx-x648899Solarisjdk-8u212-solaris8899Oracle JDK 8u211下载安装平台下载地址密码Windows x64 64位jdk-8u211-windows-x648899Windows x86 32位jdk-8u211-windows-i5868899Linux x64 64位jdk-8u211-linux-x648899Linux x86 32位jdk-8u211-linux-i5868899Linux arm 64位jdk-8u211-linux-arm648899Linux arm 32位jdk-8u211-linux-arm328899MacOS x64 64位jdk-8u211-macosx-x648899Solarisjdk-8u211-solaris8899Oracle JDK 8u202下载安装平台下载地址密码Windows x64 64位jdk-8u202-windows-x648899Windows x86 32位jdk-8u202-windows-i5868899Linux x64 64位jdk-8u202-linux-x648899Linux x86 32位jdk-8u202-linux-i5868899Linux arm 64位jdk-8u202-linux-arm648899Linux arm 32位jdk-8u202-linux-arm328899MacOS x64 64位jdk-8u202-macosx-x648899Solarisjdk-8u202-solaris8899Oracle JDK 8u201下载安装平台下载地址密码Windows x64 64位jdk-8u201-windows-x648899Windows x86 32位jdk-8u201-windows-i5868899Linux x64 64位jdk-8u201-linux-x648899Linux x86 32位jdk-8u201-linux-i5868899Linux arm 64位jdk-8u201-linux-arm648899Linux arm 32位jdk-8u201-linux-arm328899MacOS x64 64位jdk-8u201-macosx-x648899Solarisjdk-8u201-solaris8899Oracle JDK 8u192下载安装平台下载地址密码Windows x64 64位jdk-8u192-windows-x648899Windows x86 32位jdk-8u192-windows-i5868899Linux x64 64位jdk-8u192-linux-x648899Linux x86 32位jdk-8u192-linux-i5868899Linux arm 64位jdk-8u192-linux-arm648899Linux arm 32位jdk-8u192-linux-arm328899MacOS x64 64位jdk-8u192-macosx-x648899Solaris*jdk-8u192-solaris8899Oracle JDK 8u191下载安装平台下载地址密码Windows x64 64位jdk-8u191-windows-x648899Windows x86 32位jdk-8u191-windows-i5868899Linux x64 64位jdk-8u191-linux-x648899Linux x86 32位jdk-8u191-linux-i5868899Linux arm 64位jdk-8u191-linux-arm648899Linux arm 32位jdk-8u191-linux-arm328899MacOS x64 64位jdk-8u191-macosx-x648899Solarisjdk-8u191-solaris8899Oracle JDK 8u181下载安装平台下载地址密码Windows x64 64位jdk-8u181-windows-x648899Windows x86 32位jdk-8u181-windows-i5868899Linux x64 64位jdk-8u181-linux-x648899Linux x86 32位jdk-8u181-linux-i5868899Linux arm 64位jdk-8u181-linux-arm648899Linux arm 32位jdk-8u181-linux-arm328899MacOS x64 64位jdk-8u181-macosx-x648899Solaris*jdk-8u181-solaris8899Oracle JDK 8u172下载安装平台下载地址密码Windows x64 64位jdk-8u172-windows-x648899Windows x86 32位jdk-8u172-windows-i5868899Linux x64 64位jdk-8u172-linux-x648899Linux x86 32位jdk-8u172-linux-i5868899Linux arm 64位jdk-8u172-linux-arm648899Linux arm 32位jdk-8u172-linux-arm328899MacOS x64 64位jdk-8u172-macosx-x648899Solaris*jdk-8u172-solaris8899Oracle JDK 8u171下载安装平台下载地址密码Windows x64 64位jdk-8u171-windows-x648899Windows x86 32位jdk-8u171-windows-i5868899Linux x64 64位jdk-8u171-linux-x648899Linux x86 32位jdk-8u171-linux-i5868899Linux arm 64位jdk-8u171-linux-arm648899Linux arm 32位jdk-8u171-linux-arm328899MacOS x64 64位jdk-8u171-macosx-x648899Solarisjdk-8u171-solaris8899Oracle JDK 8u162下载安装平台下载地址密码Windows x64 64位jre-8u162-windows-x648899Windows x86 32位*jre-8u162-windows-i5868899Linux x64 64位jre-8u162-linux-x648899Linux x86 32位jre-8u162-linux-i5868899Linux arm 64位jdk-8u162-linux-arm648899Linux arm 32位jdk-8u162-linux-arm328899MacOS x64 64位jre-8u162-macosx-x648899Solarisjdk-8u162-solaris8899Oracle JDK 8u161下载安装平台下载地址密码Windows x64 64位jdk-8u161-windows-x648899Windows x86 32位jdk-8u161-windows-i5868899Linux x64 64位jdk-8u161-linux-x648899Linux x86 32位jdk-8u161-linux-i5868899Linux arm 64位jdk-8u161-linux-arm648899Linux arm 32位jdk-8u161-linux-arm328899MacOS x64 64位jdk-8u161-macosx-x648899Solarisjdk-8u161-solaris8899Oracle JDK 8u152下载安装平台下载地址密码Windows x64 64位jdk-8u152-windows-x648899Windows x86 32位jdk-8u152-windows-i5868899Linux x64 64位jdk-8u152-linux-x648899Linux x86 32位jdk-8u152-linux-x648899Linux arm 64位jdk-8u152-linux-arm648899Linux arm 32位jdk-8u152-linux-arm328899MacOS x64 64位jdk-8u152-macosx-x648899Solaris*jdk-8u152-solaris8899Oracle JDK 8u151下载安装平台下载地址密码Windows x64 64位jdk-8u151-windows-x648899Windows x86 32位jdk-8u151-windows-i5868899Linux x64 64位jdk-8u151-linux-x648899Linux x86 32位jdk-8u151-linux-i5868899Linux arm 64位jdk-8u151-linux-arm648899Linux arm 32位jdk-8u151-linux-arm328899MacOS x64 64位jdk-8u151-macosx-x648899Solaris*jdk-8u151-solaris8899Oracle JDK 8u144下载安装平台下载地址密码Windows x64 64位jdk-8u144-windows-x648899Windows x86 32位jdk-8u144-windows-i5868899Linux x64 64位jdk-8u144-linux-x648899Linux x86 32位jdk-8u144-linux-i5868899Linux arm 64位jdk-8u144-linux-arm648899Linux arm 32位jdk-8u144-linux-arm328899MacOS x64 64位jdk-8u144-macosx-x648899Solarisjdk-8u144-solaris8899Oracle JDK 8u141下载安装平台下载地址密码Windows x64 64位jdk-8u141-windows-x648899Windows x86 32位jdk-8u141-windows-i5868899Linux x64 64位jdk-8u141-linux-x648899Linux x86 32位jdk-8u141-linux-i5868899Linux arm 64位jdk-8u141-linux-arm648899Linux arm 32位jdk-8u141-linux-arm328899MacOS x64 64位jdk-8u141-macosx-x648899Solarisjdk-8u141-solaris8899Oracle JDK 8u131下载安装平台下载地址密码Windows x64 64位jdk-8u131-windows-x648899Windows x86 32位jdk-8u131-windows-i5868899Linux x64 64位jdk-8u131-linux-x648899Linux x86 32位jdk-8u131-linux-i5868899Linux arm 64位jdk-8u131-linux-arm648899Linux arm 32位jdk-8u131-linux-arm328899MacOS x64 64位jdk-8u131-macosx-x648899Solarisjdk-8u131-solaris8899Oracle JDK 8u121下载安装平台下载地址密码Windows x64 64位jdk-8u121-windows-x648899Windows x86 32位jdk-8u121-windows-i5868899Linux x64 64位jdk-8u121-linux-x648899Linux x86 32位jdk-8u121-linux-i5868899Linux arm 64位jdk-8u121-linux-arm648899Linux arm 32位jdk-8u121-linux-arm328899MacOS x64 64位jdk-8u121-macosx-x648899Solarisjdk-8u121-solaris8899Oracle JDK 8u112下载安装平台下载地址密码Windows x64 64位jdk-8u112-windows-x648899Windows x86 32位jdk-8u112-windows-i5868899Linux x64 64位jdk-8u112-linux-x648899Linux x86 32位jdk-8u112-linux-i5868899Linux arm 64位jdk-8u112-linux-arm648899Linux arm 32位jdk-8u112-linux-arm328899MacOS x64 64位jdk-8u112-macosx-x648899Solarisjdk-8u112-solaris8899Oracle JDK 8u111下载安装平台下载地址密码Windows x64 64位jdk-8u111-windows-x648899Windows x86 32位jdk-8u111-windows-i5868899Linux x64 64位jdk-8u111-linux-x648899Linux x86 32位jdk-8u111-linux-i5868899Linux arm 64位jdk-8u111-linux-arm648899Linux arm 32位jdk-8u111-linux-arm328899MacOS x64 64位jdk-8u111-macosx-x648899Solarisjdk-8u111-solaris8899Oracle JDK 8u92下载安装平台下载地址密码Windows x64 64位jdk-8u92-windows-x648899Windows x86 32位jdk-8u92-windows-i5868899Linux x64 64位jdk-8u92-linux-x648899Linux x86 32位jdk-8u92-linux-i5868899Linux arm 64位jdk-8u92-linux-arm648899Linux arm 32位jdk-8u92-linux-arm328899MacOS x64 64位jdk-8u92-macosx-x648899Solarisjdk-8u92-solaris8899Oracle JDK 8u91下载安装平台下载地址密码Windows x64 64位jdk-8u91-windows-x648899Windows x86 32位jdk-8u91-windows-i5868899Linux x64 64位jdk-8u91-linux-x648899Linux x86 32位jdk-8u91-linux-i5868899Linux arm 64位jdk-8u91-linux-arm648899Linux arm 32位jdk-8u91-linux-arm328899MacOS x64 64位jdk-8u91-macosx-x648899Solarisjdk-8u91-solaris8899Oracle JDK 8u77下载安装平台下载地址密码Windows x64 64位jdk-8u77-windows-x648899Windows x86 32位jdk-8u77-windows-i5868899Linux x64 64位jdk-8u77-linux-x648899Linux x86 32位jdk-8u77-linux-i5868899Linux arm 64位jdk-8u77-linux-arm648899Linux arm 32位jdk-8u77-linux-arm328899MacOS x64 64位jdk-8u77-macosx-x648899Solarisjdk-8u77-solaris8899Oracle JDK 8u74下载安装平台下载地址密码Windows x64 64位jdk-8u74-windows-x648899Windows x86 32位jdk-8u74-windows-i5868899Linux x64 64位jdk-8u74-linux-x648899Linux x86 32位jdk-8u74-linux-i5868899MacOS x64 64位jdk-8u74-macosx-x648899Solarisjdk-8u74-solaris8899Oracle JDK 8u73下载安装平台下载地址密码Windows x64 64位jdk-8u73-windows-x648899Windows x86 32位jdk-8u73-windows-i5868899Linux x64 64位jdk-8u73-linux-x648899Linux x86 32位jdk-8u73-linux-i5868899Linux arm 64位jdk-8u73-linux-arm648899Linux arm 32位jdk-8u73-linux-arm328899MacOS x64 64位jdk-8u73-macosx-x648899Solarisjdk-8u73-solaris8899Oracle JDK 8u72下载安装平台下载地址密码Windows x64 64位jdk-8u72-windows-x648899Windows x86 32位jdk-8u72-windows-i5868899Linux x64 64位jdk-8u72-linux-x648899Linux x86 32位jdk-8u72-linux-i5868899MacOS x64 64位jdk-8u72-macosx-x648899Solarisjdk-8u72-solaris8899Oracle JDK 8u71下载安装平台下载地址密码Windows x64 64位jdk-8u71-windows-x648899Windows x86 32位jdk-8u71-windows-i5868899Linux x64 64位jdk-8u71-linux-x648899Linux x86 32位jdk-8u71-linux-i5868899Linux arm 64位jdk-8u71-linux-arm648899Linux arm 32位jdk-8u71-linux-arm328899MacOS x64 64位jdk-8u71-macosx-x648899Solarisjdk-8u71-solaris8899Oracle JDK 8u66下载安装平台下载地址密码Windows x64 64位jdk-8u66-windows-x648899Windows x86 32位jdk-8u66-windows-i5868899Linux x64 64位*jdk-8u66-linux-x648899Linux x86 32位jdk-8u66-linux-i5868899MacOS x64 64位jdk-8u66-macosx-x648899Solarisjdk-8u66-solaris8899Oracle JDK 8u65下载安装平台下载地址密码Windows x64 64位jdk-8u65-windows-x648899Windows x86 32位jdk-8u65-windows-i5868899Linux x64 64位jdk-8u65-linux-x648899Linux x86 32位jdk-8u65-linux-i5868899MacOS x64 64位jdk-8u65-macosx-x648899Solarisjdk-8u65-solaris8899Oracle JDK 8u60下载安装平台下载地址密码Windows x64 64位jdk-8u60-windows-x648899Windows x86 32位jdk-8u60-windows-i5868899Linux x64 64位jdk-8u60-linux-x648899Linux x86 32位jdk-8u60-linux-i5868899MacOS x64 64位jdk-8u60-macosx-x648899Solarisjdk-8u60-solaris8899Oracle JDK 8u51下载安装平台下载地址密码Windows x64 64位jdk-8u51-windows-x648899Windows x86 32位jdk-8u51-windows-i5868899Linux x64 64位jdk-8u51-linux-x648899Linux x86 32位jdk-8u51-linux-i5868899MacOS x64 64位jdk-8u51-macosx-x648899Solarisjdk-8u51-solaris8899Oracle JDK 8u45下载安装平台下载地址密码Windows x64 64位jdk-8u45-windows-x648899Windows x86 32位jdk-8u45-windows-i5868899Linux x64 64位jdk-8u45-linux-x648899Linux x86 32位jdk-8u45-linux-i5868899MacOS x64 64位jdk-8u45-macosx-x648899Solarisjdk-8u45-solaris8899Oracle JDK 8u40下载安装平台下载地址密码Windows x64 64位jdk-8u40-windows-x648899Windows x86 32位jdk-8u40-windows-i5868899Linux x64 64位jdk-8u40-linux-x648899Linux x86 32位jdk-8u40-linux-i5868899MacOS x64 64位jdk-8u40-macosx-x648899Solarisjdk-8u40-solaris8899Oracle JDK 8u31下载安装平台下载地址密码Windows x64 64位jdk-8u31-windows-x648899Windows x86 32位jdk-8u31-windows-i5868899Linux x64 64位jdk-8u31-linux-x648899Linux x86 32位jdk-8u31-linux-i5868899MacOS x64 64位jdk-8u31-macosx-x648899Solarisjdk-8u31-solaris8899Oracle JDK 8u25下载安装平台下载地址密码Windows x64 64位jdk-8u25-windows-x648899Windows x86 32位jdk-8u25-windows-i5868899Linux x64 64位jdk-8u25-linux-x648899Linux x86 32位jdk-8u25-linux-i5868899MacOS x64 64位jdk-8u25-macosx-x648899Solarisjdk-8u25-solaris8899Oracle JDK 8u20下载安装平台下载地址密码Windows x64 64位jdk-8u20-windows-x648899Windows x86 32位jdk-8u20-windows-i5868899Linux x64 64位jdk-8u20-linux-x648899Linux x86 32位jdk-8u20-linux-i5868899MacOS x64 64位jdk-8u20-macosx-x648899Solarisjdk-8u20-solaris8899Oracle JDK 8u11下载安装平台下载地址密码Windows x64 64位jdk-8u11-windows-x648899Windows x86 32位jdk-8u11-windows-i5868899Linux x64 64位jdk-8u11-linux-x648899Linux x86 32位jdk-8u11-linux-i5868899MacOS x64 64位jdk-8u11-macosx-x648899Solarisjdk-8u11-solaris8899Oracle JDK 8u5下载安装平台下载地址密码Windows x64 64位jdk-8u5-windows-x648899Windows x86 32位jdk-8u5-windows-i5868899Linux x64 64位jdk-8u5-linux-x648899Linux x86 32位jdk-8u5-linux-i5868899MacOS x64 64位jdk-8u5-macosx-x648899Solarisjdk-8u5-solaris8899Oracle JDK 8下载安装平台下载地址密码Windows x64 64位jdk-8-windows-x648899Windows x86 32位jdk-8-windows-i5868899Linux x64 64位jdk-8-linux-x648899Linux x86 32位jdk-8-linux-i5868899MacOS x64 64位jdk-8-macosx-x648899Solarisjdk-8-solaris8899
2022年11月13日
1,070 阅读
0 评论
2 点赞
2022-11-12
intelliJ Idea 2022.2.X破解
JetBrains家族的产品现在激活也是越来越麻烦了,倒不是说激活多么麻烦,而是现在网站分享破解文件的都在强推公众号,当然,这种行为也可以理解,但是无疑给破解增加了很多障碍。本文的激活文件及激活码也是在网络上搜集的,截止到文章发布时,都能够正常使用,如果发现不能使用了,请在下面评论区告诉我。本教程适用于2022.2、2022.1.x的所有版本;2022.1.4、2022.1.3 ... 2022.1等版本都是支持的,同时,改教程不限于IDEA, JetBrains 全家桶的所有工具都适用,包括 Pycharm、WebStorm、PhpStorm、AppCode、Datagrip、CLion、RubyMine 等。一、使用说明该教程不限于IDEA,JetBrains全家桶均适合,操作流程类似。我这边亲自验证的包括intellJ Idea、WebStorm、Datagrip。本教程支持最新版IDEA 2022.2、 2022.1.x本教程适用于 Windows/Mac/Linux,本文通过Mac进行操作演示,其他系统类似,需要特殊注意的地方会在本文中针对性的说明;二、破解2.1、前期准备直接从官网下载安装包正常安装即可,如果之前有使用的破解插件,建议先删除。2.2、下载破解文件破解文件文末有提供,下载后解压到任意位置。需要注意的是,文件存储路径不要有中文或特殊符号。2.3、破解文件说明破解文件保存后,不要移动或删除文件。*.sh 为Mac和Linux的脚本*.vbs是Windows的脚本请根据自己电脑的操作系统,选择对应的脚本安装2.4、安装插件2.4.1、Windows电脑安装插件如果是Windows电脑,双击 install-all-user.vbs脚本。出现下图的 done弹框,说明成功!安装过程因电脑而异,有的很快,有的需要等几秒。2.4.2、Mac及Linux电脑安装插件mac/Linux先进入破解文件所在目录,然后执行下面的命令sh ./install.sh注意我的电脑在这一步虽然提示成功了,但是实际发现并没有安装成功,这个时候我们需要手工处理一下vmoptions打开Idea,点击【Help】→【Edit Custom VM Options】,将下面三方复制到最下面--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED --add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED -javaagent:破解文件路径/ja-netfilter.jar=jetbrains破解文件路径注意替换成自己本机路径。2.4.3、输入激活码上面完成之后,打开IDEA,输入激活码即可。下面是最新的几个激活码ideaXIZQAN09CR-eyJsaWNlbnNlSWQiOiJYSVpRQU4wOUNSIiwibGljZW5zZWVOYW1lIjoia2lkZHkgaW5zZWFtcyIsImFzc2lnbmVlTmFtZSI6IiIsImFzc2lnbmVlRW1haWwiOiIiLCJsaWNlbnNlUmVzdHJpY3Rpb24iOiIiLCJjaGVja0NvbmN1cnJlbnRVc2UiOmZhbHNlLCJwcm9kdWN0cyI6W3siY29kZSI6IlBEQiIsImZhbGxiYWNrRGF0ZSI6IjIwMjUtMDgtMDEiLCJwYWlkVXBUbyI6IjIwMjUtMDgtMDEiLCJleHRlbmRlZCI6dHJ1ZX0seyJjb2RlIjoiUFNJIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjp0cnVlfSx7ImNvZGUiOiJQUEMiLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOnRydWV9LHsiY29kZSI6IlBDV01QIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjp0cnVlfSx7ImNvZGUiOiJQUkIiLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOnRydWV9LHsiY29kZSI6IlBQUyIsImZhbGxiYWNrRGF0ZSI6IjIwMjUtMDgtMDEiLCJwYWlkVXBUbyI6IjIwMjUtMDgtMDEiLCJleHRlbmRlZCI6dHJ1ZX0seyJjb2RlIjoiSUkiLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOmZhbHNlfSx7ImNvZGUiOiJQR08iLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOnRydWV9LHsiY29kZSI6IlBTVyIsImZhbGxiYWNrRGF0ZSI6IjIwMjUtMDgtMDEiLCJwYWlkVXBUbyI6IjIwMjUtMDgtMDEiLCJleHRlbmRlZCI6dHJ1ZX0seyJjb2RlIjoiUFdTIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjp0cnVlfV0sIm1ldGFkYXRhIjoiMDEyMDIyMDgwMVBTQU4wMDAwMDUiLCJoYXNoIjoiVFJJQUw6LTEwMzUwMzQyMiIsImdyYWNlUGVyaW9kRGF5cyI6NywiYXV0b1Byb2xvbmdhdGVkIjpmYWxzZSwiaXNBdXRvUHJvbG9uZ2F0ZWQiOmZhbHNlfQ==-CoFOL4hCLVDFAdlOcxtyff4LA+HU4DIoRo+QTdjWbEuevzCGrh4ghKPWTCWT7YdMYoaaLGQfpR7DP8I2w4AxRMBH5T/KEUeNM70uTkdzIXboS460xZGLImtcte5hiD/U6k3P6NL2BVQgQwGTMRG5utlGdj1WtF/jb+yzp7+vaJiCt8uqqqXjEohapQsROTUihqtVRVkd9peAtS1gzKc39YEMnxu7Oggjuo797zMSnSswT5b4EVjgs+GJxL8RObb1o5xnKk8z4fCSRzVXD4tcVbwMXs/OVcr9+cgUYMiRCLhlHVOQJtb8F5r3IFYKFEPCPmwVAFHfmkMxC3uVmAcVsg==-MIIETDCCAjSgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTIwMTAxOTA5MDU1M1oXDTIyMTAyMTA5MDU1M1owHzEdMBsGA1UEAwwUcHJvZDJ5LWZyb20tMjAyMDEwMTkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCUlaUFc1wf+CfY9wzFWEL2euKQ5nswqb57V8QZG7d7RoR6rwYUIXseTOAFq210oMEe++LCjzKDuqwDfsyhgDNTgZBPAaC4vUU2oy+XR+Fq8nBixWIsH668HeOnRK6RRhsr0rJzRB95aZ3EAPzBuQ2qPaNGm17pAX0Rd6MPRgjp75IWwI9eA6aMEdPQEVN7uyOtM5zSsjoj79Lbu1fjShOnQZuJcsV8tqnayeFkNzv2LTOlofU/Tbx502Ro073gGjoeRzNvrynAP03pL486P3KCAyiNPhDs2z8/COMrxRlZW5mfzo0xsK0dQGNH3UoG/9RVwHG4eS8LFpMTR9oetHZBAgMBAAGjgZkwgZYwCQYDVR0TBAIwADAdBgNVHQ4EFgQUJNoRIpb1hUHAk0foMSNM9MCEAv8wSAYDVR0jBEEwP4AUo562SGdCEjZBvW3gubSgUouX8bOhHKQaMBgxFjAUBgNVBAMMDUpldFByb2ZpbGUgQ0GCCQDSbLGDsoN54TATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQELBQADggIBABqRoNGxAQct9dQUFK8xqhiZaYPd30TlmCmSAaGJ0eBpvkVeqA2jGYhAQRqFiAlFC63JKvWvRZO1iRuWCEfUMkdqQ9VQPXziE/BlsOIgrL6RlJfuFcEZ8TK3syIfIGQZNCxYhLLUuet2HE6LJYPQ5c0jH4kDooRpcVZ4rBxNwddpctUO2te9UU5/FjhioZQsPvd92qOTsV+8Cyl2fvNhNKD1Uu9ff5AkVIQn4JU23ozdB/R5oUlebwaTE6WZNBs+TA/qPj+5/we9NH71WRB0hqUoLI2AKKyiPw++FtN4Su1vsdDlrAzDj9ILjpjJKA1ImuVcG329/WTYIKysZ1CWK3zATg9BeCUPAV1pQy8ToXOq+RSYen6winZ2OO93eyHv2Iw5kbn1dqfBw1BuTE29V2FJKicJSu8iEOpfoafwJISXmz1wnnWL3V/0NxTulfWsXugOoLfv0ZIBP1xH9kmf22jjQ2JiHhQZP7ZDsreRrOeIQ/c4yR8IQvMLfC0WKQqrHu5ZzXTH4NO3CwGWSlTY74kE91zXB5mwWAx1jig+UXYc2w4RkVhy0//lOmVya/PEepuuTTI4+UJwC7qbVlh5zfhj8oTNUXgN0AOc+Q0/WFPl1aw5VV/VrO8FCoB15lFVlpKaQ1Yh+DVU8ke+rt9Th0BCHXe0uZOEmH0nOnH/0onDwebstormQA01VSISIH-eyJsaWNlbnNlSWQiOiJRQTAxVlNJU0lIIiwibGljZW5zZWVOYW1lIjoia2lkZHkgaW5zZWFtcyIsImFzc2lnbmVlTmFtZSI6IiIsImFzc2lnbmVlRW1haWwiOiIiLCJsaWNlbnNlUmVzdHJpY3Rpb24iOiIiLCJjaGVja0NvbmN1cnJlbnRVc2UiOmZhbHNlLCJwcm9kdWN0cyI6W3siY29kZSI6IldTIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjpmYWxzZX0seyJjb2RlIjoiUFNJIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjp0cnVlfSx7ImNvZGUiOiJQQ1dNUCIsImZhbGxiYWNrRGF0ZSI6IjIwMjUtMDgtMDEiLCJwYWlkVXBUbyI6IjIwMjUtMDgtMDEiLCJleHRlbmRlZCI6dHJ1ZX0seyJjb2RlIjoiUFdTIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjp0cnVlfV0sIm1ldGFkYXRhIjoiMDEyMDIyMDgwMVBTQU4wMDAwMDUiLCJoYXNoIjoiVFJJQUw6MTg0ODI4OTEzMCIsImdyYWNlUGVyaW9kRGF5cyI6NywiYXV0b1Byb2xvbmdhdGVkIjpmYWxzZSwiaXNBdXRvUHJvbG9uZ2F0ZWQiOmZhbHNlfQ==-PqVYTlv/yp1AaLCz/i/e//sf0n6LD6uvJTy7/jl9F2A3RJjjMvWCDeCx8tK8PZep5I+GosApeiQdzkv7aG/TfjJRiHzdF0PgJaATxwaf9hDQx7fWT2zALtrOqT89C0Qe/OsLKM9mSFm9y5ul4JW7MQSfCDMtm8B8fUQ5ZFu0nB3XcI4YnMPDjKGaifDUdY2B0u7k29CQ4JLnYxC8HL4644GG+T8F+mNkOoDDNTSE8LmcEKWXYKPzD+YEJ5flLyHc5OVtYDPF2MQ/wsbwMVf7l1O22pkr8jKdyHLzbtaeIXojlOI8fvqnXSSetFmdMa/eRC0kHHf42BZ7VQ8etd599g==-MIIETDCCAjSgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTIwMTAxOTA5MDU1M1oXDTIyMTAyMTA5MDU1M1owHzEdMBsGA1UEAwwUcHJvZDJ5LWZyb20tMjAyMDEwMTkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCUlaUFc1wf+CfY9wzFWEL2euKQ5nswqb57V8QZG7d7RoR6rwYUIXseTOAFq210oMEe++LCjzKDuqwDfsyhgDNTgZBPAaC4vUU2oy+XR+Fq8nBixWIsH668HeOnRK6RRhsr0rJzRB95aZ3EAPzBuQ2qPaNGm17pAX0Rd6MPRgjp75IWwI9eA6aMEdPQEVN7uyOtM5zSsjoj79Lbu1fjShOnQZuJcsV8tqnayeFkNzv2LTOlofU/Tbx502Ro073gGjoeRzNvrynAP03pL486P3KCAyiNPhDs2z8/COMrxRlZW5mfzo0xsK0dQGNH3UoG/9RVwHG4eS8LFpMTR9oetHZBAgMBAAGjgZkwgZYwCQYDVR0TBAIwADAdBgNVHQ4EFgQUJNoRIpb1hUHAk0foMSNM9MCEAv8wSAYDVR0jBEEwP4AUo562SGdCEjZBvW3gubSgUouX8bOhHKQaMBgxFjAUBgNVBAMMDUpldFByb2ZpbGUgQ0GCCQDSbLGDsoN54TATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQELBQADggIBABqRoNGxAQct9dQUFK8xqhiZaYPd30TlmCmSAaGJ0eBpvkVeqA2jGYhAQRqFiAlFC63JKvWvRZO1iRuWCEfUMkdqQ9VQPXziE/BlsOIgrL6RlJfuFcEZ8TK3syIfIGQZNCxYhLLUuet2HE6LJYPQ5c0jH4kDooRpcVZ4rBxNwddpctUO2te9UU5/FjhioZQsPvd92qOTsV+8Cyl2fvNhNKD1Uu9ff5AkVIQn4JU23ozdB/R5oUlebwaTE6WZNBs+TA/qPj+5/we9NH71WRB0hqUoLI2AKKyiPw++FtN4Su1vsdDlrAzDj9ILjpjJKA1ImuVcG329/WTYIKysZ1CWK3zATg9BeCUPAV1pQy8ToXOq+RSYen6winZ2OO93eyHv2Iw5kbn1dqfBw1BuTE29V2FJKicJSu8iEOpfoafwJISXmz1wnnWL3V/0NxTulfWsXugOoLfv0ZIBP1xH9kmf22jjQ2JiHhQZP7ZDsreRrOeIQ/c4yR8IQvMLfC0WKQqrHu5ZzXTH4NO3CwGWSlTY74kE91zXB5mwWAx1jig+UXYc2w4RkVhy0//lOmVya/PEepuuTTI4+UJwC7qbVlh5zfhj8oTNUXgN0AOc+Q0/WFPl1aw5VV/VrO8FCoB15lFVlpKaQ1Yh+DVU8ke+rt9Th0BCHXe0uZOEmH0nOnH/0onDdatagripFJVUHLU3X1-eyJsaWNlbnNlSWQiOiJGSlZVSExVM1gxIiwibGljZW5zZWVOYW1lIjoia2lkZHkgaW5zZWFtcyIsImFzc2lnbmVlTmFtZSI6IiIsImFzc2lnbmVlRW1haWwiOiIiLCJsaWNlbnNlUmVzdHJpY3Rpb24iOiIiLCJjaGVja0NvbmN1cnJlbnRVc2UiOmZhbHNlLCJwcm9kdWN0cyI6W3siY29kZSI6IlBEQiIsImZhbGxiYWNrRGF0ZSI6IjIwMjUtMDgtMDEiLCJwYWlkVXBUbyI6IjIwMjUtMDgtMDEiLCJleHRlbmRlZCI6dHJ1ZX0seyJjb2RlIjoiREIiLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOmZhbHNlfSx7ImNvZGUiOiJQU0kiLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOnRydWV9LHsiY29kZSI6IlBXUyIsImZhbGxiYWNrRGF0ZSI6IjIwMjUtMDgtMDEiLCJwYWlkVXBUbyI6IjIwMjUtMDgtMDEiLCJleHRlbmRlZCI6dHJ1ZX1dLCJtZXRhZGF0YSI6IjAxMjAyMjA4MDFQU0FOMDAwMDA1IiwiaGFzaCI6IlRSSUFMOi0yNDc1NjQ2MTIiLCJncmFjZVBlcmlvZERheXMiOjcsImF1dG9Qcm9sb25nYXRlZCI6ZmFsc2UsImlzQXV0b1Byb2xvbmdhdGVkIjpmYWxzZX0=-IWA8NqxA1nOvfTGTVFX4PNAWRswj0hTsqntqWZcqcYFz/zIobEAYmHm0Lks82E0mPcNCzt0LPW6BfUZCI8f4r5E1nsonNS40bDv44qAcjBmQaLf5XxZLyoKRzl7YacDuqql+NY3tInFBX8Q4PQu56aVsS6DOZmeoO4fC66Qtwg2z+A+kvVpSlB3+1Fqww7SHZMuQbLlEOVSHqO2tf4bJVTIMH/OSMph5CpY4uJ8iv7yeBX+WQpcOy4tv1AZNEY9tIKI8nRVbnVnZaAf5R2ng1AduGCVSOaU1/ElLPReBvXTG6gZHtjKDHlAy2kq6JIH/CCeG/3ZkDLB0GzB29aSgHA==-MIIETDCCAjSgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTIwMTAxOTA5MDU1M1oXDTIyMTAyMTA5MDU1M1owHzEdMBsGA1UEAwwUcHJvZDJ5LWZyb20tMjAyMDEwMTkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCUlaUFc1wf+CfY9wzFWEL2euKQ5nswqb57V8QZG7d7RoR6rwYUIXseTOAFq210oMEe++LCjzKDuqwDfsyhgDNTgZBPAaC4vUU2oy+XR+Fq8nBixWIsH668HeOnRK6RRhsr0rJzRB95aZ3EAPzBuQ2qPaNGm17pAX0Rd6MPRgjp75IWwI9eA6aMEdPQEVN7uyOtM5zSsjoj79Lbu1fjShOnQZuJcsV8tqnayeFkNzv2LTOlofU/Tbx502Ro073gGjoeRzNvrynAP03pL486P3KCAyiNPhDs2z8/COMrxRlZW5mfzo0xsK0dQGNH3UoG/9RVwHG4eS8LFpMTR9oetHZBAgMBAAGjgZkwgZYwCQYDVR0TBAIwADAdBgNVHQ4EFgQUJNoRIpb1hUHAk0foMSNM9MCEAv8wSAYDVR0jBEEwP4AUo562SGdCEjZBvW3gubSgUouX8bOhHKQaMBgxFjAUBgNVBAMMDUpldFByb2ZpbGUgQ0GCCQDSbLGDsoN54TATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQELBQADggIBABqRoNGxAQct9dQUFK8xqhiZaYPd30TlmCmSAaGJ0eBpvkVeqA2jGYhAQRqFiAlFC63JKvWvRZO1iRuWCEfUMkdqQ9VQPXziE/BlsOIgrL6RlJfuFcEZ8TK3syIfIGQZNCxYhLLUuet2HE6LJYPQ5c0jH4kDooRpcVZ4rBxNwddpctUO2te9UU5/FjhioZQsPvd92qOTsV+8Cyl2fvNhNKD1Uu9ff5AkVIQn4JU23ozdB/R5oUlebwaTE6WZNBs+TA/qPj+5/we9NH71WRB0hqUoLI2AKKyiPw++FtN4Su1vsdDlrAzDj9ILjpjJKA1ImuVcG329/WTYIKysZ1CWK3zATg9BeCUPAV1pQy8ToXOq+RSYen6winZ2OO93eyHv2Iw5kbn1dqfBw1BuTE29V2FJKicJSu8iEOpfoafwJISXmz1wnnWL3V/0NxTulfWsXugOoLfv0ZIBP1xH9kmf22jjQ2JiHhQZP7ZDsreRrOeIQ/c4yR8IQvMLfC0WKQqrHu5ZzXTH4NO3CwGWSlTY74kE91zXB5mwWAx1jig+UXYc2w4RkVhy0//lOmVya/PEepuuTTI4+UJwC7qbVlh5zfhj8oTNUXgN0AOc+Q0/WFPl1aw5VV/VrO8FCoB15lFVlpKaQ1Yh+DVU8ke+rt9Th0BCHXe0uZOEmH0nOnH/0onDphpstormZEW8I0GZC6-eyJsaWNlbnNlSWQiOiJaRVc4STBHWkM2IiwibGljZW5zZWVOYW1lIjoia2lkZHkgaW5zZWFtcyIsImFzc2lnbmVlTmFtZSI6IiIsImFzc2lnbmVlRW1haWwiOiIiLCJsaWNlbnNlUmVzdHJpY3Rpb24iOiIiLCJjaGVja0NvbmN1cnJlbnRVc2UiOmZhbHNlLCJwcm9kdWN0cyI6W3siY29kZSI6IlBTSSIsImZhbGxiYWNrRGF0ZSI6IjIwMjUtMDgtMDEiLCJwYWlkVXBUbyI6IjIwMjUtMDgtMDEiLCJleHRlbmRlZCI6dHJ1ZX0seyJjb2RlIjoiUENXTVAiLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOnRydWV9LHsiY29kZSI6IlBTIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjpmYWxzZX0seyJjb2RlIjoiUFBTIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjp0cnVlfSx7ImNvZGUiOiJQV1MiLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOnRydWV9XSwibWV0YWRhdGEiOiIwMTIwMjIwODAxUFNBTjAwMDAwNSIsImhhc2giOiJUUklBTDo5NzM1MDQ0MzkiLCJncmFjZVBlcmlvZERheXMiOjcsImF1dG9Qcm9sb25nYXRlZCI6ZmFsc2UsImlzQXV0b1Byb2xvbmdhdGVkIjpmYWxzZX0=-UVh/hXrqvTl8syZGee3sWsipAUdbN7XBg2XpFkSNh+oGBGzwvsj2k5A3v2LGO6+46rDU/HdG14jHYnT1iDMum5K5I7aCVYOIcJcmSAViwNDLqvTcOjMOdDZ4XS3aDg/KeaX5PozDa+H/KgHdGL07nKCBuhopAesiGXZC0RYerwCnYX1DjG9b/02igSyCuIYfsCO8d3GL+/ESz+sI8FiXmyZB7N413SJDGnp9klF51Kz469YzWZNEIXp9zg3Wu/Vsq8zlW9mGCFmsyiu0btTkS5PZfxp5BebH8Ef5SvPP7SyLJ2Q0FqeCJ9ESXp6aNBW8lmTVY+R0ZpqgsBOwbkq2xg==-MIIETDCCAjSgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTIwMTAxOTA5MDU1M1oXDTIyMTAyMTA5MDU1M1owHzEdMBsGA1UEAwwUcHJvZDJ5LWZyb20tMjAyMDEwMTkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCUlaUFc1wf+CfY9wzFWEL2euKQ5nswqb57V8QZG7d7RoR6rwYUIXseTOAFq210oMEe++LCjzKDuqwDfsyhgDNTgZBPAaC4vUU2oy+XR+Fq8nBixWIsH668HeOnRK6RRhsr0rJzRB95aZ3EAPzBuQ2qPaNGm17pAX0Rd6MPRgjp75IWwI9eA6aMEdPQEVN7uyOtM5zSsjoj79Lbu1fjShOnQZuJcsV8tqnayeFkNzv2LTOlofU/Tbx502Ro073gGjoeRzNvrynAP03pL486P3KCAyiNPhDs2z8/COMrxRlZW5mfzo0xsK0dQGNH3UoG/9RVwHG4eS8LFpMTR9oetHZBAgMBAAGjgZkwgZYwCQYDVR0TBAIwADAdBgNVHQ4EFgQUJNoRIpb1hUHAk0foMSNM9MCEAv8wSAYDVR0jBEEwP4AUo562SGdCEjZBvW3gubSgUouX8bOhHKQaMBgxFjAUBgNVBAMMDUpldFByb2ZpbGUgQ0GCCQDSbLGDsoN54TATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQELBQADggIBABqRoNGxAQct9dQUFK8xqhiZaYPd30TlmCmSAaGJ0eBpvkVeqA2jGYhAQRqFiAlFC63JKvWvRZO1iRuWCEfUMkdqQ9VQPXziE/BlsOIgrL6RlJfuFcEZ8TK3syIfIGQZNCxYhLLUuet2HE6LJYPQ5c0jH4kDooRpcVZ4rBxNwddpctUO2te9UU5/FjhioZQsPvd92qOTsV+8Cyl2fvNhNKD1Uu9ff5AkVIQn4JU23ozdB/R5oUlebwaTE6WZNBs+TA/qPj+5/we9NH71WRB0hqUoLI2AKKyiPw++FtN4Su1vsdDlrAzDj9ILjpjJKA1ImuVcG329/WTYIKysZ1CWK3zATg9BeCUPAV1pQy8ToXOq+RSYen6winZ2OO93eyHv2Iw5kbn1dqfBw1BuTE29V2FJKicJSu8iEOpfoafwJISXmz1wnnWL3V/0NxTulfWsXugOoLfv0ZIBP1xH9kmf22jjQ2JiHhQZP7ZDsreRrOeIQ/c4yR8IQvMLfC0WKQqrHu5ZzXTH4NO3CwGWSlTY74kE91zXB5mwWAx1jig+UXYc2w4RkVhy0//lOmVya/PEepuuTTI4+UJwC7qbVlh5zfhj8oTNUXgN0AOc+Q0/WFPl1aw5VV/VrO8FCoB15lFVlpKaQ1Yh+DVU8ke+rt9Th0BCHXe0uZOEmH0nOnH/0onDappcodeVKZAU5B43Y-eyJsaWNlbnNlSWQiOiJWS1pBVTVCNDNZIiwibGljZW5zZWVOYW1lIjoia2lkZHkgaW5zZWFtcyIsImFzc2lnbmVlTmFtZSI6IiIsImFzc2lnbmVlRW1haWwiOiIiLCJsaWNlbnNlUmVzdHJpY3Rpb24iOiIiLCJjaGVja0NvbmN1cnJlbnRVc2UiOmZhbHNlLCJwcm9kdWN0cyI6W3siY29kZSI6IlBTSSIsImZhbGxiYWNrRGF0ZSI6IjIwMjUtMDgtMDEiLCJwYWlkVXBUbyI6IjIwMjUtMDgtMDEiLCJleHRlbmRlZCI6dHJ1ZX0seyJjb2RlIjoiUENXTVAiLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOnRydWV9LHsiY29kZSI6IkFDIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjpmYWxzZX0seyJjb2RlIjoiUFNXIiwiZmFsbGJhY2tEYXRlIjoiMjAyNS0wOC0wMSIsInBhaWRVcFRvIjoiMjAyNS0wOC0wMSIsImV4dGVuZGVkIjp0cnVlfSx7ImNvZGUiOiJQV1MiLCJmYWxsYmFja0RhdGUiOiIyMDI1LTA4LTAxIiwicGFpZFVwVG8iOiIyMDI1LTA4LTAxIiwiZXh0ZW5kZWQiOnRydWV9XSwibWV0YWRhdGEiOiIwMTIwMjIwODAxUFNBTjAwMDAwNSIsImhhc2giOiJUUklBTDo4MjMzNzExNjIiLCJncmFjZVBlcmlvZERheXMiOjcsImF1dG9Qcm9sb25nYXRlZCI6ZmFsc2UsImlzQXV0b1Byb2xvbmdhdGVkIjpmYWxzZX0=-RrHOvDeSwoPjgraFAh3zGXDfoJ1hXOrZJroE/sKj/96CimH7YlD0An0lUEGvSEsovDprHWDZ4q2Wq11vukassv+avD1JHuurWhjp1mpIOvOntp2VHbBV9+iUxsr5ET+zJaIl0WiaRd6n75LeU4ZhxT4y6Da7cLbrqEawH+zjUuHzs60mUaqZ/5+j3qfVlwnx+iwPPu3dFtXWvADmcV/nb2jwnrJcLx9HMGVbS3yrgNoXhqXI+YRsYdNwYXbF7yVseGoxjKHQDBWTd3dAjWES/iC0s8xzLD7VXGkd3gIDJ0cc8cgzmCUr3E/QABzU1CdpqY0hyCedCvb9IOwfmTH+ug==-MIIETDCCAjSgAwIBAgIBDTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTIwMTAxOTA5MDU1M1oXDTIyMTAyMTA5MDU1M1owHzEdMBsGA1UEAwwUcHJvZDJ5LWZyb20tMjAyMDEwMTkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCUlaUFc1wf+CfY9wzFWEL2euKQ5nswqb57V8QZG7d7RoR6rwYUIXseTOAFq210oMEe++LCjzKDuqwDfsyhgDNTgZBPAaC4vUU2oy+XR+Fq8nBixWIsH668HeOnRK6RRhsr0rJzRB95aZ3EAPzBuQ2qPaNGm17pAX0Rd6MPRgjp75IWwI9eA6aMEdPQEVN7uyOtM5zSsjoj79Lbu1fjShOnQZuJcsV8tqnayeFkNzv2LTOlofU/Tbx502Ro073gGjoeRzNvrynAP03pL486P3KCAyiNPhDs2z8/COMrxRlZW5mfzo0xsK0dQGNH3UoG/9RVwHG4eS8LFpMTR9oetHZBAgMBAAGjgZkwgZYwCQYDVR0TBAIwADAdBgNVHQ4EFgQUJNoRIpb1hUHAk0foMSNM9MCEAv8wSAYDVR0jBEEwP4AUo562SGdCEjZBvW3gubSgUouX8bOhHKQaMBgxFjAUBgNVBAMMDUpldFByb2ZpbGUgQ0GCCQDSbLGDsoN54TATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQELBQADggIBABqRoNGxAQct9dQUFK8xqhiZaYPd30TlmCmSAaGJ0eBpvkVeqA2jGYhAQRqFiAlFC63JKvWvRZO1iRuWCEfUMkdqQ9VQPXziE/BlsOIgrL6RlJfuFcEZ8TK3syIfIGQZNCxYhLLUuet2HE6LJYPQ5c0jH4kDooRpcVZ4rBxNwddpctUO2te9UU5/FjhioZQsPvd92qOTsV+8Cyl2fvNhNKD1Uu9ff5AkVIQn4JU23ozdB/R5oUlebwaTE6WZNBs+TA/qPj+5/we9NH71WRB0hqUoLI2AKKyiPw++FtN4Su1vsdDlrAzDj9ILjpjJKA1ImuVcG329/WTYIKysZ1CWK3zATg9BeCUPAV1pQy8ToXOq+RSYen6winZ2OO93eyHv2Iw5kbn1dqfBw1BuTE29V2FJKicJSu8iEOpfoafwJISXmz1wnnWL3V/0NxTulfWsXugOoLfv0ZIBP1xH9kmf22jjQ2JiHhQZP7ZDsreRrOeIQ/c4yR8IQvMLfC0WKQqrHu5ZzXTH4NO3CwGWSlTY74kE91zXB5mwWAx1jig+UXYc2w4RkVhy0//lOmVya/PEepuuTTI4+UJwC7qbVlh5zfhj8oTNUXgN0AOc+Q0/WFPl1aw5VV/VrO8FCoB15lFVlpKaQ1Yh+DVU8ke+rt9Th0BCHXe0uZOEmH0nOnH/0onD激活码失效或者需要更多激活码,可以在评论区留言。
2022年11月12日
4,354 阅读
41 评论
9 点赞
1
...
11
12
13
...
53