当前位置: 首页 > news >正文

SpringCloud微服务实战——搭建企业级开发框架(五十三):微信小程序授权登录增加多租户可配置界面

  GitEgg框架集成weixin-java-miniapp工具包以实现微信小程序相关接口调用功能,weixin-java-miniapp底层支持多租户扩展。每个小程序都有唯一的appid,weixin-java-miniapp的多租户实现并不是以租户标识TenantId来区分的,而是在接口调用时,传入appid,动态切换ThreadLocal的appid来实现多租户的。并且其多个微信小程序的配置,都是在配置yml文件中的,在实际业务运营过程中,如果需要新增多租户小程序就修改配置文件显然是不合适的。
  现在我们需要结合weixin-java-miniapp的多租户实现整合到我们的框架中,使多租户可通过系统配置界面来新增多租户小程序。前面我们讲了如何集成以及如何使用weixin-java-miniapp实现微信授权登录及账号绑定等,现在只需要在原来的基础上增加数据配置存储,在服务启动时由原先的读取配置文件加载相应的微信小程序接口实例,修改为可以通过读取配置文件和读取缓存配置来生成相应的微信小程序接口实例。

一、新增微信小程序配置界面

1. 微信小程序配置数据库设计

  在数据库设计的时候,我们需要知道微信小程序授权时,哪些字段需要配置,是可选字段还是必填字段,这里我们通过weixin-java-miniapp的springboot工程配置文件可知,所需字段有:

# 公众号配置(必填)
wx.miniapp.appid = appId
wx.miniapp.secret = @secret
wx.miniapp.token = @token
wx.miniapp.aesKey = @aesKey
wx.miniapp.msgDataFormat = @msgDataFormat                  # 消息格式,XML或者JSON.
# 存储配置redis(可选)
# 注意: 指定redis.host值后不会使用容器注入的redis连接(JedisPool)
wx.miniapp.config-storage.type = Jedis                     # 配置类型: Memory(默认), Jedis, RedisTemplate
wx.miniapp.config-storage.key-prefix = wa                  # 相关redis前缀配置: wa(默认)
wx.miniapp.config-storage.redis.host = 127.0.0.1
wx.miniapp.config-storage.redis.port = 6379
# http客户端配置
wx.miniapp.config-storage.http-client-type=HttpClient      # http客户端类型: HttpClient(默认), OkHttp, JoddHttp
wx.miniapp.config-storage.http-proxy-host=
wx.miniapp.config-storage.http-proxy-port=
wx.miniapp.config-storage.http-proxy-username=
wx.miniapp.config-storage.http-proxy-password=

  根据我们的设计,配置文件中需要增加租户字段,我们需要兼容即使用配置文件来配置微信小程序,又可以使用配置界面将微信小程序配置信息配置到数据库中,同时,增加md5字段配置,用于在读取配置时比较配置信息是否有更改。所以,保存微信小程序配置的数据库设计如下:

CREATE TABLE `t_wechat_miniapp`  (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id',`miniapp_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信小程序名称',`appid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信小程序appid',`secret` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信小程序secret',`token` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信小程序token',`aes_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信小程序aesKey',`msg_data_format` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息格式,XML或者JSON',`storage_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '配置类型: Memory(默认), Jedis, RedisTemplate',`key_prefix` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '相关redis前缀配置: wa(默认)',`redis_host` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Redis服务器地址',`redis_port` int(11) NULL DEFAULT NULL COMMENT 'Redis服务器端口',`http_client_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'http客户端类型: HttpClient(默认), OkHttp, JoddHttp',`http_proxy_host` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'http_proxy_host',`http_proxy_port` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'http_proxy_port',`http_proxy_username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'http_proxy_username',`http_proxy_password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'http_proxy_password',`status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '状态 1有效 0禁用',`md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'MD5',`comments` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',`creator` bigint(20) NULL DEFAULT NULL COMMENT '创建者',`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',`operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者',`del_flag` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否删除',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信小程序配置' ROW_FORMAT = DYNAMIC;
2. 通过代码生成器生成微信小程序配置的增删查改代码

  通过代码生成器根据新设计的表进行CRUD代码生成,详细步骤不再赘述,前面有详细讲解如何根据数据库表设计生成前后端代码。只是这里需要增加业务逻辑处理,以及更新到缓存配置。

  • 新增时,将配置信息添加到Redis,并且需要根据系统是否开启多租户来判断生成缓存key
    /*** 创建微信小程序配置* @param miniapp* @return*/@Overridepublic boolean createMiniapp(CreateMiniappDTO miniapp) {Miniapp miniappEntity = BeanCopierUtils.copyByClass(miniapp, Miniapp.class);try {String miniappEntityStr = JsonUtils.objToJson(miniappEntity);miniappEntity.setMd5(SecureUtil.md5(miniappEntityStr));} catch (Exception e) {log.error("创建微信小程序配置时,md5加密失败:{}", e);throw new BusinessException("创建微信小程序配置时,md5加密失败:" + e);}boolean result = this.save(miniappEntity);if (result){// 更新到缓存Miniapp miniappEntityLocal = this.getById(miniappEntity.getId());MiniappDTO miniappDTO = BeanCopierUtils.copyByClass(miniappEntityLocal, MiniappDTO.class);this.addOrUpdateMiniappCache(miniappDTO);}return result;}
  • 编辑时,需要更新Redis配置信息,因为会有key也同时修改的情况,所以,需要先删除旧的配置信息,再新增新的配置信息
    /*** 更新微信小程序配置* @param miniapp* @return*/@Overridepublic boolean updateMiniapp(UpdateMiniappDTO miniapp) {Miniapp miniappEntity = BeanCopierUtils.copyByClass(miniapp, Miniapp.class);Miniapp miniappEntityOld = this.getById(miniappEntity.getId());try {String miniappEntityStr = JsonUtils.objToJson(miniappEntity);miniappEntity.setMd5(SecureUtil.md5(miniappEntityStr));} catch (Exception e) {log.error("创建微信小程序配置时,md5加密失败:{}", e);throw new BusinessException("创建微信小程序配置时,md5加密失败:" + e);}boolean result = this.updateById(miniappEntity);if (result){// 把旧的删掉MiniappDTO miniappDTOOld = BeanCopierUtils.copyByClass(miniappEntityOld, MiniappDTO.class);this.deleteMiniappCache(miniappDTOOld);// 更新到缓存Miniapp miniappEntityLocal = this.getById(miniappEntity.getId());MiniappDTO miniappDTO = BeanCopierUtils.copyByClass(miniappEntityLocal, MiniappDTO.class);this.addOrUpdateMiniappCache(miniappDTO);}return result;}
  • 删除时,直接根据条件生成缓存key,然后进行删除即可
    /*** 删除微信小程序配置* @param miniappId* @return*/@Overridepublic boolean deleteMiniapp(Long miniappId) {// 从缓存删除Miniapp miniappEntity = this.getById(miniappId);MiniappDTO miniappDTO = BeanCopierUtils.copyByClass(miniappEntity, MiniappDTO.class);this.deleteMiniappCache(miniappDTO);// 从数据库中删除boolean result = this.removeById(miniappId);return result;}
  • 新增/更新缓存的公共方法
    private void addOrUpdateMiniappCache(MiniappDTO miniappDTO) {try {String redisKey = MiniappConstant.WX_MINIAPP_CONFIG_KEY;if (enable) {redisKey = MiniappConstant.WX_MINIAPP_TENANT_CONFIG_KEY + miniappDTO.getAppid();}redisTemplate.opsForHash().put(redisKey, miniappDTO.getTenantId().toString(), JsonUtils.objToJson(miniappDTO));// wxMaService增加configthis.addConfig(miniappDTO);} catch (Exception e) {log.error("初始化微信小程序配置失败:{}" , e);}}
  • 删除缓存的公共方法
    private void deleteMiniappCache(MiniappDTO miniappDTO) {try {String redisKey = MiniappConstant.WX_MINIAPP_CONFIG_KEY;if (enable) {redisKey = MiniappConstant.WX_MINIAPP_TENANT_CONFIG_KEY + miniappDTO.getAppid();}redisTemplate.opsForHash().delete(redisKey, miniappDTO.getTenantId().toString(), JsonUtils.objToJson(miniappDTO));// wxMaService删除configthis.removeConfig(miniappDTO);} catch (Exception e) {log.error("初始化微信小程序配置失败:{}" , e);}}
3. 修改相关配置文件,使微信小程序配置既支持配置文件又支持数据库配置
  • 新增GitEggWxMaRedissonConfigImpl类继承自WxMaRedissonConfigImpl,增加我们需要的租户配置字段:configKey、tenantId、md5。
/*** @author GitEgg* @date 2023/7/21*/
public class GitEggWxMaRedissonConfigImpl extends WxMaRedissonConfigImpl {protected String configKey;protected String tenantId;protected String md5;public GitEggWxMaRedissonConfigImpl(@NonNull RedissonClient redissonClient, String keyPrefix) {super(redissonClient, keyPrefix);}public GitEggWxMaRedissonConfigImpl(@NonNull RedissonClient redissonClient) {super(redissonClient);}
......
}
  • 修改WxMaProperties类,增加我们需要的字段tenantId,因为configKey和md5是系统生成的,只有动态可配置时才会用到这几个字段去判断,这里如果是配置文件配置,修改后生效必须重启系统,所以这里不需要这几个字段。
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {private List<Config> configs;@Datapublic static class Config {/*** 租户*/private Long tenantId;/*** 设置微信小程序的appid*/private String appid;/*** 设置微信小程序的Secret*/private String secret;/*** 设置微信小程序消息服务器配置的token*/private String token;/*** 设置微信小程序消息服务器配置的EncodingAESKey*/private String aesKey;/*** 消息格式,XML或者JSON*/private String msgDataFormat;}}
  • 修改WxMaConfiguration类,我们使用自己定义的缓存方式和Config类进行相关操作,这里的缓存我们使用Redisson。
    @Beanpublic WxMaService wxMaService() {List<WxMaProperties.Config> configs = this.properties.getConfigs();//已添加缓存配置,如果配置文件没有,那么在缓存新增时,仍然可以setConfigs
//        if (configs == null) {
//            throw new WxRuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!");
//        }WxMaService maService = new WxMaServiceImpl();if (null != configs){maService.setMultiConfigs(configs.stream().map(a -> {
//                    WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
//                    WxMaDefaultConfigImpl config = new WxMaRedisConfigImpl(new JedisPool());GitEggWxMaRedissonConfigImpl config = new GitEggWxMaRedissonConfigImpl(redissonClient);// 使用上面的配置时,需要同时引入jedis-lock的依赖,否则会报类无法找到的异常config.setTenantId(null != a.getTenantId() ? a.getTenantId().toString() : AuthConstant.DEFAULT_TENANT_ID.toString());config.setConfigKey(config.getTenantId() + StrPool.UNDERLINE + a.getAppid());config.setAppid(a.getAppid());config.setSecret(a.getSecret());config.setToken(a.getToken());config.setAesKey(a.getAesKey());config.setMsgDataFormat(a.getMsgDataFormat());return config;}).collect(Collectors.toMap( GitEggWxMaRedissonConfigImpl::getConfigKey, a -> a, (o, n) -> o)));}return maService;}

二、服务启动时加载微信小程序配置信息

1. 新增加载方法,服务启动时加载配置数据需要排除多租户插件,之所以在启动时加载配置信息到缓存,是因为配置的微服务和小程序相关功能的服务不是同一个服务,所以将缓存作为相关配置的存储。
  • MiniappMapper中新增initMiniappList数据库查询方法,一定要加@InterceptorIgnore(tenantLine = “true”)注解,表示此查询不需要多租户控制,在服务启动时不区分租户加载所有配置。
    /*** 排除多租户插件查询微信配置列表* @param miniappDTO* @return*/@InterceptorIgnore(tenantLine = "true")List<MiniappDTO> initMiniappList(@Param("miniapp") QueryMiniappDTO miniappDTO);
    <!-- 不区分租户查询微信小程序配置信息 --><select id="getMiniapp" resultType="com.gitegg.boot.extension.wx.miniapp.dto.MiniappDTO" parameterType="com.gitegg.boot.extension.wx.miniapp.dto.QueryMiniappDTO">SELECT<include refid="Base_Column_List"/>FROM t_wechat_miniappWHERE del_flag = 0<if test="miniapp.miniappName != null and miniapp.miniappName != ''">AND miniapp_name = #{miniapp.miniappName}</if><if test="miniapp.appid != null and miniapp.appid != ''">AND appid = #{miniapp.appid}</if><if test="miniapp.secret != null and miniapp.secret != ''">AND secret = #{miniapp.secret}</if><if test="miniapp.status != null and miniapp.status != ''">AND status = #{miniapp.status}</if>ORDER BY id DESC</select>
  • MiniappServiceImpl中新增initMiniappList接口的实现方法,缓存配置的增删查改方法也在此类中实现,这里不再赘述,更多了解可以查看框架代码。
    /*** 初始化微信小程序配置表列表* @return*/@Overridepublic void initMiniappList() {QueryMiniappDTO miniappDTO = new QueryMiniappDTO();miniappDTO.setStatus(String.valueOf(GitEggConstant.ENABLE));// 这里初始化所有的配置,不再只初始化已启用的配置List<MiniappDTO> miniappInfoList = miniappMapper.initMiniappList(miniappDTO);// 判断是否开启了租户模式,如果开启了,那么需要按租户进行分类存储if (enable) {Map<String, List<MiniappDTO>> miniappListMap =miniappInfoList.stream().collect(Collectors.groupingBy(MiniappDTO::getAppid));miniappListMap.forEach((key, value) -> {String redisKey = MiniappConstant.WX_MINIAPP_TENANT_CONFIG_KEY + key;redisTemplate.delete(redisKey);addMiniapp(redisKey, value);});} else {redisTemplate.delete(MiniappConstant.WX_MINIAPP_CONFIG_KEY);addMiniapp(MiniappConstant.WX_MINIAPP_CONFIG_KEY, miniappInfoList);}}
  • 在InitExtensionCacheRunner系统配置加载类中新增initMiniappList的调用
/*** 容器启动完成加载扩展信息数据到缓存* @author GitEgg*/
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component
public class InitExtensionCacheRunner implements CommandLineRunner {private final IJustAuthConfigService justAuthConfigService;private final IJustAuthSourceService justAuthSourceService;private final IMailChannelService mailChannelService;private final IMiniappService miniappService;@Overridepublic void run(String... args) {log.info("InitExtensionCacheRunner running");// 初始化第三方登录主配置justAuthConfigService.initJustAuthConfigList();// 初始化第三方登录 第三方配置justAuthSourceService.initJustAuthSourceList();// 初始化邮件配置信息mailChannelService.initMailChannelList();// 初始化微信配置信息miniappService.initMiniappList();}
}
2. 实现动态选择某个租户微信小程序接口的方法,我们需要在保证原先读取配置文件的方式仍然可用的基础上扩展读取数据库缓存配置信息,所以,在接口实现时需要充分考虑原先配置方式可用。
  • 通过appid获取组装后的key值,在WxMaService中存储着默认以appid为key值的配置configMap,这里我们将key值修改为tenantId_appid的格式,作为多租户的扩展,同时在获取配置信息时,也需要传入多租户信息。
  • 如果前端传了租户,那么先使用前端的租户,如果没有传租户,那么从系统中查询租户
  • 从缓存获取配置对象,如果md5配置和系统配置不一样,那么需要重新add
  • 缓存配置中没有也需要直接返回,因为有可能是配置文件配置的
  • 取缓存中所有appid的配置租户,如果存在多个租户,那么提示错误,让前端选择租户;如果只有一个租户,那么返回
    /*** 通过appid获取appid,忽略租户插件* @param miniappId* @return*/@Overridepublic String getMiniappId(String miniappId) {if (enable) {// 如果前端传了租户,那么先使用前端的租户,如果没有传租户,那么从系统中查询租户String tenantId = GitEggAuthUtils.getTenantId();if (!StringUtils.isEmpty(tenantId)){String miniappStr = (String) redisTemplate.opsForHash().get(MiniappConstant.WX_MINIAPP_TENANT_CONFIG_KEY + miniappId, tenantId);if (!StringUtils.isEmpty(miniappStr)){// 转为系统配置对象try {// 从缓存获取配置对象,如果md5配置和系统配置不一样,那么需要重新addMiniappDTO miniappDTO = JsonUtils.jsonToPojo(miniappStr, MiniappDTO.class);return this.ifConfig(miniappDTO);} catch (Exception e) {log.error("获取微信小程序配置时发生异常:{}", e);throw new BusinessException("获取微信小程序配置时发生异常。");}}// 缓存配置中没有也需要直接返回,因为有可能是配置文件配置的return tenantId + StrPool.UNDERLINE + miniappId;} else {String redisKey = MiniappConstant.WX_MINIAPP_TENANT_CONFIG_KEY + miniappId;// 取缓存中所有appid的配置租户,如果存在多个租户,那么提示错误,让前端选择租户;如果只有一个租户,那么返回List<Object> values = redisTemplate.opsForHash().values(redisKey);if (!CollectionUtils.isEmpty(values)){if (values.size() > GitEggConstant.Number.ONE){throw new BusinessException("此小程序配置在多个租户下,请选择所需要操作的租户。");}String miniappConfig = (String) values.stream().findFirst().orElse(null);try {MiniappDTO miniappConfigDTO = JsonUtils.jsonToPojo(miniappConfig, MiniappDTO.class);return this.ifConfig(miniappConfigDTO);} catch (Exception e) {log.error("获取缓存小程序配置失败:{}", e);throw new BusinessException("小程序已被禁用,请联系管理员");}}else{return AuthConstant.DEFAULT_TENANT_ID + StrPool.UNDERLINE + miniappId;}}} else {return AuthConstant.DEFAULT_TENANT_ID + StrPool.UNDERLINE + miniappId;}}
3. 修改原先的切换租户方法调用,在WxMaUserController中,将原来的wxMaService.switchover(appid)修改为我们自己的实现miniappService.switchover(appid),其它有切换租户调用的都改为此方法。
        if (!miniappService.switchover(appid)) {throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid));}
4. 微信小程序前端代码不需要修改,还是按照前面章节的说明进行调用即可。在请求中加入appid,在请求头中加入租户id。
import request from '@/common/utils/request'const wechatLoginApi = {Login: '/extension/wx/user/'
}export default wechatLoginApi/*** 微信登录* @param {Object} appId* @param {Object} parameter*/
export function wechatLogin (appId, parameter) {return request({url: wechatLoginApi.Login + appId + '/login',method: 'get',params: parameter})
}/*** 获取微信信息* @param {Object} appId* @param {Object} parameter*/
export function wechatInfo (appId, parameter) {return request({url: wechatLoginApi.Login + appId + '/info',method: 'get',params: parameter})
}/*** 获取手机号* @param {Object} appId* @param {Object} parameter*/
export function wechatPhone (appId, parameter) {return request({url: wechatLoginApi.Login + appId + '/phone',method: 'get',params: parameter})
}

  在修改配置时,一定需要注意权限问题,一般情况下,在不同的租户下不允许配置相同的微信小程序,因为appid是唯一的,在发布微信小程序后,微信小程序是唯一的。当然也有特殊的情况,比如,同一个小程序作为多个租户相同的商户管理端,那么在此时,需要让用户在前端选择输入租户标识以确定登录用户属于那个租户,即多个租户共用同一个微信小程序。

GitEgg-Cloud是一款基于SpringCloud整合搭建的企业级微服务应用开发框架,开源项目地址:

Gitee: https://gitee.com/wmz1930/GitEgg

GitHub: https://github.com/wmz1930/GitEgg

相关文章:

SpringCloud微服务实战——搭建企业级开发框架(五十三):微信小程序授权登录增加多租户可配置界面

GitEgg框架集成weixin-java-miniapp工具包以实现微信小程序相关接口调用功能&#xff0c;weixin-java-miniapp底层支持多租户扩展。每个小程序都有唯一的appid&#xff0c;weixin-java-miniapp的多租户实现并不是以租户标识TenantId来区分的&#xff0c;而是在接口调用时&#…...

Stability AI推出Stable Diffusion XL 1.0,文本到图像模型

Stability AI宣布推出Stable Diffusion XL 1.0&#xff0c;这是一个文本到图像的模型&#xff0c;该公司将其描述为迄今为止“最先进的”版本。 Stability AI表示&#xff0c;SDXL 1.0能生成更加鲜明准确的色彩&#xff0c;在对比度、光线和阴影方面做了增强&#xff0c;可生成…...

B076-项目实战--宠物上下架 展示 领养 收购订单

目录 上下架功能提供后台宠物列表实现 前台展示前台宠物列表和详情展示店铺展示 领养分析前台后端PetControllerPetServiceImpl 订单需求分析可能产生订单的模块订单模块额外功能 订单设计表设计流程设计 集成基础代码收购订单创建订单前端后端 上下架功能提供 后台宠物列表实…...

【iOS】—— 持久化

文章目录 数据持久化的目的iOS中数据持久化方案数据持久化方式分类内存缓存磁盘缓存 沙盒机制获取应用程序的沙盒路径沙盒目录的获取方式 持久化数据存储方式XML属性列表Preferences偏好设置&#xff08;UserDefaults&#xff09;数据库存储什么是序列化和反序列化&#xff0c;…...

教程 - 在 Vue3+Ts 中引入 CesiumJS 的最佳实践@2023

1. 本篇适用范围与目的 1.1. 适用范围 严格使用 Vue3 TypeScript 的前端项目&#xff0c;包管理器默认使用 pnpm 构建工具使用 Vite4 使用原生 CesiumJS 依赖做应用开发 客户端渲染&#xff0c;因为我不太熟悉 Vue 的服务端渲染&#xff0c;有本篇的介绍后&#xff0c;熟悉…...

最优化方法

一. 图论 1.最小生成树 图的生成树是它的一颗含有其所有顶点的无环连通子图,一 幅加权图的最小生成树(MST)是它的一颗权值(树中的所有边的权值之和) 最小的生成树 • 适用场景&#xff1a;道路规划、通讯网络规划、管道铺设、电线布设等 题目数据 kruskal算法 稀疏图&#x…...

Mongodb 多文档聚合操作处理方法二(Map-reduce 函数)

聚合 聚合操作处理多个文档并返回计算结果。您可以使用聚合操作来&#xff1a; 将多个文档中的值分组在一起。 对分组数据执行操作以返回单个结果。 分析数据随时间的变化。 要执行聚合操作&#xff0c;您可以使用&#xff1a; 聚合管道 单一目的聚合方法 Map-reduce 函…...

ant design vue j-modal 修改高度

问题描述 今天在项目中遇到关于j-modal组件修改弹窗大小问题&#xff0c;我尝试使用直接使用:height"300"&#xff0c;没用效果&#xff0c;弹窗大小依然和没改之前一样&#xff0c;后来找到了这种方式可以去修改j-modal弹窗大小&#xff0c;下面来看下代码实现&…...

spring学习笔记七

一、自动装配 1.1、BookDao接口和实现类 public interface BookDao {void save(); } public class BookDaoImpl implements BookDao {public void save(){System.out.println("book dao save......");} } 1.2、BookService接口和实现类 public interface BookSer…...

hw技战法整理参考

目录 IP溯源反制 账户安全策略及预警 蜜罐部署联动方案...

uniapp 全局数据(globalData)的设置,获取,更改

globalData&#xff0c;这是一种简单的全局变量机制。这套机制在uni-app里也可以使用&#xff0c;并且全端通用 因为uniapp基本上都是将页面&#xff0c;或者页面中相同的部分&#xff0c;进行组件化&#xff0c;所以会存在父&#xff0c;子&#xff0c;&#xff08;子&#xf…...

Profinet转EtherNet/IP网关连接AB PLC的应用案例

西门子S7-1500 PLC&#xff08;profinet&#xff09;与AB PLC以太网通讯&#xff08;EtherNet/IP&#xff09;。本文主要介绍捷米特JM-EIP-PN的Profinet转EtherNet/IP网关&#xff0c;连接西门子S7-1500 PLC与AB PLC 通讯的配置过程&#xff0c;供大家参考。 1, 新建工程&…...

Python组合模式介绍、使用方法

一、Python组合模式介绍 概念&#xff1a; 组合模式(Composite Pattern)是一种结构型设计模式&#xff0c;它通过将对象组合成树状结构来表示“整体/部分”层次结构&#xff0c;让客户端可以以相同的方式处理单个对象和组合对象。 功能&#xff1a; 统一对待组合对象和叶子对…...

生成模型和判别模型工作原理介绍

您解决的大多数机器学习和深度学习问题都是从生成模型和判别模型中概念化的。在机器学习中,人们可以清楚地区分两种建模类型: 将图像分类为狗或猫属于判别性建模生成逼真的狗或猫图像是一个生成建模问题神经网络被采用得越多,生成域和判别域就增长得越多。要理解基于这些模型…...

shardingsphere读写分离配置

注&#xff1a; 如果是升级之前的单库单表&#xff0c;要将之前的 数据库接池 druid-spring-boot-starter 注释掉&#xff0c;换成 druid&#xff0c;否则无法连接数据库。 原因&#xff1a; 因为数据连接池的starter&#xff08;比如druid&#xff09;可能会先加载并且其创…...

登录报错 “msg“:“Request method ‘GET‘ not supported“,“code“:500

1. 登录失败 2. 排查原因, 把 PostMapping请求注释掉, 或改成GetMapping请求就不会报错 3. 找到SecurityConfig.java , 新增 .antMatchers("/**/**").permitAll() //匹配允许所有路径 4. 登录成功...

Python 日期和时间

Python 日期和时间 Python 程序能用很多方式处理日期和时间&#xff0c;转换日期格式是一个常见的功能。 Python 提供了一个 time 和 calendar 模块可以用于格式化日期和时间。 时间间隔是以秒为单位的浮点小数。 每个时间戳都以自从1970年1月1日午夜&#xff08;历元&…...

pytorch的发展历史,与其他框架的联系

我一直是这样以为的&#xff1a;pytorch的底层实现是c(这一点没有问题&#xff0c;见下边的pytorch结构图),然后这个部分顺理成章的被命名为torch,并提供c接口,我们在python中常用的是带有python接口的&#xff0c;所以被称为pytorch。昨天无意中看到Torch是由lua语言写的&…...

Kibana-elastic--Elastic Stack--ELK Stack

Kibana 是什么&#xff1f; | Elastic 将数据转变为结果、响应和解决方案 使用 Kibana 针对大规模数据快速运行数据分析&#xff0c;以实现可观测性、安全和搜索。对来自任何来源的任何数据进行全面透彻的分析&#xff0c;从威胁情报到搜索分析&#xff0c;从日志到应用程序监测…...

Docker复杂命令便捷操作

启动所有状态为Created的容器 要启动所有状态为"created"的Docker容器&#xff0c;可以使用以下命令&#xff1a; docker container start $(docker container ls -aq --filter "statuscreated")上述命令执行了以下步骤&#xff1a; docker container l…...

Python中的datetime模块

time模块用于取得UNIX纪元时间戳&#xff0c;并加以处理。但是&#xff0c;如果以方便的格式显示日期&#xff0c;或对日期进行算数运算&#xff0c;就应该使用datetime模块。 目录 1. datetime数据类型 1&#xff09; datetime.datetime.now()表示特定时刻 2&#xff09;da…...

Flutter - 微信朋友圈、十字滑动效果(微博/抖音个人中心效果)

demo 地址: https://github.com/iotjin/jh_flutter_demo 代码不定时更新&#xff0c;请前往github查看最新代码 前言 一般APP都有类似微博/抖音个人中心的效果&#xff0c;支持上下拉刷新&#xff0c;并且顶部有个图片可以下拉放大&#xff0c;图片底部是几个tab&#xff0c;可…...

MySQL检索数据和排序数据

目录 一、select语句 1.检索单个列&#xff08;SELECT 列名 FROM 表名;&#xff09; 2.检索多个列&#xff08;SELECT 列名1&#xff0c;列名2&#xff0c;列名3 FROM 表名;&#xff09; 3.检索所有的列&#xff08;SELECT * FROM 表名;&#xff09; 4.检索不同的行&#x…...

通过STM32内部ADC将烟雾传感器发送的信号值显示在OLED上

一.CubeMX配置 首先我们在CubeMX配置ADC1, 设置一个定时器TIM2定时1s采样一次以及刷新一次OLED&#xff0c; 打开IIC用于驱动OLED显示屏。 二.程序 在Keil5中添加好oled的显示库&#xff0c;以及用来显示的函数、初始化函数、清屏函数等。在主程序中初始化oled,并将其清屏。…...

ZEPHYR 快速开发指南

简介 国内小伙伴在学习zephyr的时候&#xff0c;有以下几个痛点&#xff1a; 学习门槛过高github访问不畅&#xff0c;下载起来比较费劲。 这篇文章将我自己踩的坑介绍一下&#xff0c;顺便给大家优化一些地方&#xff0c;避免掉所有的坑。 首先用virtualbox 来安装一个ubu…...

【FPGA + 串口】功能完备的串口测试模块,三种模式:自发自收、交叉收发、内源

【FPGA 串口】功能完备的串口测试模块&#xff0c;三种模式&#xff1a;自发自收、交叉收发、内源 VIO 控制单元 wire [1:0] mode;vio_uart UART_VIO (.clk(ad9361_l_clk), // input wire clk.probe_out0(mode) // output wire [1 : 0] probe_out0 );将 mod…...

初步了解预训练语言模型BERT

本文字数&#xff1a;&#xff1a;4024字 预计阅读时间&#xff1a;12分钟 BERT是由Google提出的预训练语言模型&#xff0c;它基于transformer架构&#xff0c;被广泛应用于自然语言处理领域&#xff0c;是当前自然语言处理领域最流行的预训练模型之一。而了解BERT需要先了解注…...

Android Hook系统 Handler 消息实现

前言 主线程的Handler 主要依赖于 ActivityThread&#xff0c;Android是消息驱动&#xff0c;比如view的刷新&#xff0c;activity的创建等&#xff0c;如果能打印系统层Handler消息日志&#xff0c;就需要对于系统层的Handler 进行Hook 原理 ActivityThread中 mH对象主要负责…...

R语言从入门到精通之【R语言的使用】

系列文章目录 1.R语言从入门到精通之【R语言介绍】 2.R语言从入门到精通之【R语言下载与安装】 3.R语言从入门到精通之【R语言的使用】 文章目录 系列文章目录一、新手上路1.R语句构成2.获取帮助3.工作空间二、包1.包的安装2.实践应用总结一、新手上路 1.R语句构成 R语句由函…...

WPF实战学习笔记29-登录数据绑定,编写登录服务

添加登录绑定字段、命令、方法 修改对象&#xff1a;Mytodo.ViewModels.ViewModels using Mytodo.Service; using Prism.Commands; using Prism.Events; using Prism.Mvvm; using Prism.Services.Dialogs; using System; using System.CodeDom.Compiler; using System.Collec…...

做游戏排行榜的网站模板/怎么在网上销售

本文实例讲述了Python实现的拟合二元一次函数功能。分享给大家供大家参考&#xff0c;具体如下&#xff1a;背景&#xff1a;使用scipy拟合一元二次函数。参考&#xff1a;HYRY Studio-《用Python做科学计算》代码&#xff1a;# -*- coding:utf-8 -*-#! python3import numpy as…...

建设网站需要哪些设备/职业技能培训学校

weboffice js操作word导读&#xff1a;就爱阅读网友为您分享以下“js操作word”的资讯&#xff0c;希望对您有所帮助&#xff0c;感谢您对92的支持!1.保存html页面到word***************************************************************************************单元格1单元格…...

企业门户定制网站建设公司/seo 0xu

2019独角兽企业重金招聘Python工程师标准>>> 一、初始化本地git项目 1. git init 2. git add -A 3. git commit -m "初始化仓库"####二、在github上创建项目 如&#xff1a; https://github.com/zkj/easyjava.git三、将github上的项目pull下来 git pull o…...

个人未授权做的网站/广州百度竞价外包

背景 我想使用带有Inception-Resnet_v2的keras来预测病理图像.我已经训练了模型并得到了.hdf5文件.由于病理图像非常大(例如&#xff1a;20,000 x 20,000像素),因此我必须扫描图像以获得用于预测的小补丁. 我想使用python2.7的多处理库来加速预测过程.主要思想是使用不同的子进…...

网站开发需求书/seo优化方式

3X家庭净水计划 1. 双膜单出水净水机(润佳系列 RO-18) 雷谛净水器主要参数&#xff1a; 双膜单出水&#xff0c;节能节水 智能控制显示 韩国进口RO和UF膜组件 马来西亚椰壳活性炭 欧洲标准工艺流程与结构设计 水压要求&#xff1a;0.1-0.4MPa 净水流量&#xff1a;50G 冲洗方式…...

网站实现多模板切换/网络营销策划ppt范例

Java Arrays.sort源代码解析   Java Arrays中提供了对所有类型的排序。其中主要分为Primitive(8种基本类型)和Object两大类。 基本类型&#xff1a;采用调优的快速排序&#xff1b; 对象类型&#xff1a;采用改进的归并排序。 一、对于基本类型源码分析如下&#xff08;以int…...