当前位置: 首页 > 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…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问&#xff1a;说说对 IP 数据报中 TTL 的理解&#xff1f;我们都知道&#xff0c;IP 数据报由首部和数据两部分组成&#xff0c;首部又分为两部分&#xff1a;固定部分和可变部分&#xff0c;共占 20 字节&#xff0c;而即将讨论的 TTL 就位于首…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...