【Lilishop商城】No4-3.业务逻辑的代码开发,涉及到:会员B端第三方登录的开发-微信小程序登录接口开发

news2025/1/10 16:12:37

仅涉及后端,全部目录看顶部专栏,代码、文档、接口路径在: 

 

【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客


全篇会结合业务介绍重点设计逻辑,其中重点包括接口类、业务类,具体的结合源代码分析,源码读起来也不复杂~

谨慎:源代码中有一些注释是错误的,有的注释意思完全相反,有的注释对不上号,我在阅读过程中就顺手更新了,并且在我不会的地方添加了新的注释,所以在读源代码过程中一定要谨慎啊! 

目录

A1.会员登录模块

B1.微信小程序登录接口开发

业务逻辑:

代码逻辑:

        1.实体类Connect   及  分别通过 openid 和 unionid 与账号进行绑定

        2.获取系统里的微信小程序配置信息

        ​​​​​​​3.header的uuid作为 key 从缓存中获取的微信用户 


A1.会员登录模块

B1.微信小程序登录接口开发

微信小程序登录就一个接口,需要传参:临时登录凭证code、手机号码的对称解密的目标密文encryptedData、手机号码的对称解密算法初始向量iv、微信头像、微信用户昵称。header 里面传递 uuid

header的uuid:此次用户登录的uuid,用来防止多次调用接口导致多次调用第三方接口。前端需要根据后端的返回值来判断是否更新 uuid。        

临时登录凭证code:用来获取用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台帐号) 和 会话密钥 session_key。拿到后用于解密和绑定帐号见:小程序登录 | 微信开放文档

手机号码的对称解密的目标密文encryptedData、手机号码的对称解密算法初始向量iv:是用来获取用户手机号码的,拿到后用于获取账号货或注册。此处使用的是旧的方法,具体可见之前的 No4-1 C1.微信小程序 文档说明 ;

微信头像、微信用户昵称: 拿到后用于账号注册,wx.getUserProfile(Object object) | 微信开放文档

接口接受到参数后会判断是否能拿到账号,拿到这直接返回账号登陆的 Token ;若拿不到则注册账号并返回账号登录的Token。

业务逻辑:

在介绍业务逻辑时,会涉及到一些其他代码结构,有需要说明的就用绿色底纹标注,然后在后面的代码逻辑里面详细介绍。

此时需要新增一个实体类 Connect ,是联合登陆关联表 li_connect 的,用来将账号和第三方用户唯一标识绑定的。

ConnectServiceImpl 类:仅使用的 mybatis-plus 的,没有自定义mapper,针对实体类 li_connect

  1. 先以header的uuid作为 key 从缓存中获取的微信用户信息,若获取到只直接 2.,若获取不到则获取系统里的微信小程序配置信息,然后再结合前端传入的临时登录凭证code ,作为入参调用微信提供的获取接口,从响应中拿到微信小程序端用户基本信息openid、unionid、session_key。并将这些信息以header的uuid作为 key 存到缓存里面
  2. 根据session_key、手机号码对称解密的目标密文encryptedData、手机号码的对称解密算法初始向量iv进行解密,拿到用户手机号码。【解密逻辑看微信提供的~在No4-1里说明了】
  3. 查询手机号码绑定的账号,如果存在会员,则分别通过 openid 和 unionid 与账号进行绑定并存储到li_connect表,并且返回账号的登录 token;如果不存在账号则 4.
  4. 如果不存在会员,则根据手机号、微信头像、微信用户昵称注册会员,用户名也是手机号码拼接的,并且分别通过 openid 和 unionid 与账号进行绑定并存储到li_connect表,由于是注册账号所以也要处理会员注册的小事件【和平台注册的一样不再说明了】,并且最后返回账号的登录 token;

代码逻辑:

//cn.lili.controller.passport.connect.MiniProgramBuyerController
@RestController
@RequestMapping("/buyer/passport/connect/miniProgram")
@Api(tags = "买家端,小程序登录接口")
public class MiniProgramBuyerController {

    @Autowired
    public ConnectService connectService;

    @GetMapping("/auto-login")
    @ApiOperation(value = "小程序登录/自动注册")
    public ResultMessage<Token> autoLogin(@RequestHeader String uuid, WechatMPLoginParams params) {
        params.setUuid(uuid);
        return ResultUtil.data(this.connectService.miniProgramAutoLogin(params));
    }
。。。
}
//cn.lili.modules.connect.serviceimpl.ConnectServiceImpl
@Slf4j
@Service
public class ConnectServiceImpl extends ServiceImpl<ConnectMapper, Connect> implements ConnectService {

    @Autowired
    private SettingService settingService;
    @Autowired
    private MemberService memberService;
    @Autowired
    private MemberTokenGenerate memberTokenGenerate;
    @Autowired
    private Cache cache;
    /**
     * RocketMQ 配置
     */
    @Autowired
    private RocketmqCustomProperties rocketmqCustomProperties;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    @Transactional
    public Token miniProgramAutoLogin(WechatMPLoginParams params) {

        Object cacheData = cache.get(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid());
        Map<String, String> map = new HashMap<>(3);
        if (cacheData == null) {
            //通过前端传入的微信返回的登录凭证code ,去换取微信小程序端用户基本信息
            JSONObject json = this.getConnect(params.getCode());
            //存储session key 后续登录用得到
            String sessionKey = json.getStr("session_key");
            String unionId = json.getStr("unionid");
            String openId = json.getStr("openid");
            map.put("sessionKey", sessionKey);
            map.put("unionId", unionId);
            map.put("openId", openId);
            cache.put(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid(), map, 900L);
        } else {
            map = (Map<String, String>) cacheData;
        }
        //手机号 绑定 且 自动登录
        return this.phoneMpBindAndLogin(map.get("sessionKey"), params, map.get("openId"), map.get("unionId"));
    }


    /**
     * 通过微信返回等code 获取openid 等信息
     *
     * @param code 微信code
     * @return 微信返回的信息
     */
    public JSONObject getConnect(String code) {
        //获取系统里的微信小程序配置
        WechatConnectSettingItem setting = this.getWechatMPSetting();
        String url = "https://api.weixin.qq.com/sns/jscode2session?" +
                "appid=" + setting.getAppId() + "&" +
                "secret=" + setting.getAppSecret() + "&" +
                "js_code=" + code + "&" +
                "grant_type=authorization_code";
        String content = HttpUtils.doGet(url, "UTF-8", 100, 1000);
        log.error(content);
        return JSONUtil.parseObj(content);
    }

    /**
     * 手机号 绑定 且 自动登录
     *
     * @param sessionKey 微信sessionKey
     * @param params     微信小程序自动登录参数
     * @param openId     微信openid
     * @param unionId    微信unionid
     * @return token
     */
    @Transactional(rollbackFor = Exception.class)
    public Token phoneMpBindAndLogin(String sessionKey, WechatMPLoginParams params, String openId, String unionId) {
        String encryptedData = params.getEncryptedData();
        String iv = params.getIv();
        //使用旧版本解密方式,获取微信信息
        JSONObject userInfo = this.getUserInfo(encryptedData, sessionKey, iv);
        log.info("联合登陆返回:{}", userInfo.toString());
        //拿到手机号码
        String phone = (String) userInfo.get("purePhoneNumber");

        //手机号登录
        LambdaQueryWrapper<Member> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Member::getMobile, phone);
        //查询手机号码绑定的账号
        Member member = memberService.getOne(lambdaQueryWrapper);
        //如果存在会员,则进行绑定微信openid 和 unionid,并且登录
        if (member != null) {
            //会员绑定 绑定微信小程序
            this.bindMpMember(openId, unionId, member);
            return memberTokenGenerate.createToken(member, true);
        }

        //如果没有会员,则根据手机号注册会员
        Member newMember = new Member("m" + phone, "111111", phone, params.getNickName(), params.getImage());
        memberService.save(newMember);
        newMember = memberService.findByUsername(newMember.getUsername());
        //会员绑定 绑定微信小程序
        this.bindMpMember(openId, unionId, newMember);
        //发送会员注册信息
        applicationEventPublisher.publishEvent(new TransactionCommitSendMQEvent("new member register", rocketmqCustomProperties.getMemberTopic(), MemberTagsEnum.MEMBER_REGISTER.name(), newMember));
        return memberTokenGenerate.createToken(newMember, true);
    }

    /**
     * 解密,获取微信信息,此类的逻辑是根据微信的加密数据解密算法逻辑开发的,具体见:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
     *  此方法是旧版本~~~详见:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/deprecatedGetPhoneNumber.html
     *  新版本是拿到code后调用HTTPS接口来获取手机号码~~~这个就很简单就是用 HttpUtils 调用接口获取响应就可以了~
     *  详见:
     *      获取手机号码组件:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html
     *      获取手机号码接口:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html
     *
     * @param encryptedData 加密信息
     * @param sessionKey    微信sessionKey
     * @param iv            微信揭秘参数
     * @return 用户信息
     */
    public JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {

        log.info("encryptedData:{},sessionKey:{},iv:{}", encryptedData, sessionKey, iv);
        //被加密的数据
        byte[] dataByte = Base64.getDecoder().decode(encryptedData);
        //加密秘钥
        byte[] keyByte = Base64.getDecoder().decode(sessionKey);
        //偏移量
        byte[] ivByte = Base64.getDecoder().decode(iv);
        try {
            //如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            //初始化  需要导入包
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            //初始化
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, StandardCharsets.UTF_8);
                return JSONUtil.parseObj(result);
            }
        } catch (Exception e) {
            log.error("解密,获取微信信息错误", e);
        }
        throw new ServiceException(ResultCode.USER_CONNECT_ERROR);
    }

    /**
     * 会员绑定 绑定微信小程序
     * <p>
     * 如果openid 已经绑定其他账号,则这里不作处理,如果未绑定,则绑定最新的会员
     * 这样,微信小程序注册之后,其他app 公众号页面,都可以实现绑定自动登录功能
     * </p>
     *
     * @param openId  微信openid
     * @param unionId 微信unionid
     * @param member  会员
     */
    private void bindMpMember(String openId, String unionId, Member member) {

        //如果 unionid 不为空  则为账号绑定unionid
        if (CharSequenceUtil.isNotEmpty(unionId)) {
            LambdaQueryWrapper<Connect> lambdaQueryWrapper = new LambdaQueryWrapper();
            lambdaQueryWrapper.eq(Connect::getUnionId, unionId);
            lambdaQueryWrapper.eq(Connect::getUnionType, ConnectEnum.WECHAT.name());
            List<Connect> connects = this.list(lambdaQueryWrapper);
            //只有为绑定过的才会绑定,已绑定过的不会再次绑定!!!!
            if (connects.isEmpty()) {
                Connect connect = new Connect();
                connect.setUnionId(unionId);
                connect.setUserId(member.getId());
                connect.setUnionType(ConnectEnum.WECHAT.name());
                this.save(connect);
            }
        }//如果 openid 不为空  则为账号绑定openid
        if (CharSequenceUtil.isNotEmpty(openId)) {
            LambdaQueryWrapper<Connect> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(Connect::getUnionId, openId);
            lambdaQueryWrapper.eq(Connect::getUnionType, ConnectEnum.WECHAT_MP_OPEN_ID.name());
            List<Connect> connects = this.list(lambdaQueryWrapper);
            //只有为绑定过的才会绑定,已绑定过的不会再次绑定!!!!
            if (connects.isEmpty()) {
                Connect connect = new Connect();
                connect.setUnionId(openId);
                connect.setUserId(member.getId());
                connect.setUnionType(ConnectEnum.WECHAT_MP_OPEN_ID.name());
                this.save(connect);
            }
        }
    }

    /**
     * 获取微信小程序配置
     *
     * @return 微信小程序配置
     */
    private WechatConnectSettingItem getWechatMPSetting() {
        //从数据库中拿到微信 联合登陆设置
        Setting setting = settingService.get(SettingEnum.WECHAT_CONNECT.name());
        //然后将设置转化成使用JavaBean对象。此对象里面是用list存放多种微信应用设置的
        WechatConnectSetting wechatConnectSetting = JSONUtil.toBean(setting.getSettingValue(), WechatConnectSetting.class);

        if (wechatConnectSetting == null) {
            throw new ServiceException(ResultCode.WECHAT_CONNECT_NOT_EXIST);
        }
        //寻找对应对微信小程序登录配置
        for (WechatConnectSettingItem wechatConnectSettingItem : wechatConnectSetting.getWechatConnectSettingItems()) {
            //拿到微信小程序应用的配置
            if (wechatConnectSettingItem.getClientType().equals(ClientTypeEnum.WECHAT_MP.name())) {
                return wechatConnectSettingItem;
            }
        }
        throw new ServiceException(ResultCode.WECHAT_CONNECT_NOT_EXIST);
    }

。。。
}

1.实体类Connect   及  分别通过 openid 和 unionid 与账号进行绑定

先说实体类 Connect, 里面重要的就是下面这三个字段,每一个联合登录关联表都会关联一个账号id、联合(第三方)用户id、联合(第三方)类型。只要联合用户id未关联过账号就会添加一份关联。

联合类型包括:微信的各个产品下的不同openid(小程序、移动应用、网站应用)、微信unionid、QQ的各个产品、微博(不知道微博有没有多个应用,还没用过)、支付宝等等,需要了解第三方后细分哦。

同一个 userid 可能和多个联合用户id 绑定哦。 

但是要注意,通常来说一个现实用户在任何端使用任意第三方进行授权登录时应该都关联同一个账号,但是shop里面可不是,所以需要注意~~~~【为啥?请看完所有的会员登录方式,就明白了】

    @ApiModelProperty("用户id")
    private String userId;

    @ApiModelProperty("联合登录id")
    private String unionId;

    /**
     * @see cn.lili.modules.connect.entity.enums.ConnectEnum
     */
    @ApiModelProperty(value = "联合登录类型")
    private String unionType;

所以  分别通过 openid 和 unionid 与账号进行绑定  这个逻辑也就不用说明了,就是将第三方的用户id与平台账号绑定。

绑定也是先判断此联合用户id是否已存在,也就是是否已绑定过账号,没有则会绑定。

2.获取系统里的微信小程序配置信息

 配置信息都包括appId、appSecret、clientType,这些都是必须向微信申请的,审核通过后微信下发的,每个产品都有自己唯一的,例如shop项目里面微信小程序的和微信网站应用的都是不一样的!

这些信息是属于平台的,所以可以交给运营端管理,由于这些都是小数据单开一个数据表不值得,所以可以使用运营M端的系统设置逻辑,根据 K:V保存起来,V里面保存为 json类型的。​​​​​​​

使用时根据 clientType 区分是哪个产品的。

//WECHAT_CONNECT 的
{
    "wechatConnectSettingItems":[
        {
            "clientType":"PC",
            "appId":"XXXXXXX",
            "appSecret":"XXXXXXX"
        },
        {
            "clientType":"H5",
            "appId":"XXXXXXX",
            "appSecret":"XXXXXXX"
        },
        {
            "clientType":"WECHAT_MP",
            "appId":"XXXXXXX",
            "appSecret":"XXXXXXX"
        }
    ]
}

 

3.header的uuid作为 key 从缓存中获取的微信用户 

其实不缓存也不会有问题,这里添加缓存是避免多次调用 this.getConnect(params.getCode());方法,该方法最终是会调用微信提供的接口的。而微信的接口都是有使用频率的!!!是有限制的!!!避免由于其他错误导致次数过多后面用户无法进行调用。

 

另一方面也是如果小程序端调用次数频率过多时,能过提高性能,毕竟同一个用户拿到的信息是一样的,所以先存储起来,如果真的碰到多次频繁调用,就能够直接获取缓存中的信息使用了。

这就是要多注意,前端需要根据后端的响应情况来更新或清除 uuid 哦,否则该逻辑就没用~~~

如果此次登录授权的响应是未成功,那么就不用清除 uuid ,如果登录成功就能清除 uuid 了

055e0a05fddf48d1895e4522ced70e0e.png (1080×2408)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/118469.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工厂明火烟雾视频监控识别 烟火自动识别预警 yolo

工厂明火烟雾视频监控识别 烟火自动识别预警通过pythonyolo网络深度学习模型可以自动识别监控区域内的烟火&#xff0c;如pythonyolo网络深度学习模型发现火焰烟火可以立即抓拍告警。Python是一种由Guido van Rossum开发的通用编程语言&#xff0c;它很快就变得非常流行&#x…

Flink系列-2、Flink架构体系

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 大数据系列文章目录 官方网址&#xff1a;https://flink.apache.org/ 学习资料&#xff1a;https://flink-learning.org.cn/ 目录Flink中的重要…

[ web基础知识点 ] 解决端口被占用的问题(关闭连接)(杀死进程)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

什么是文件描述符

Linux内核在各种不同的文件系统格式之上做了一个抽象层&#xff0c;使得文件、目录、读写访问等概念成为抽象层的概念&#xff0c;因此各种文件系统看起来用起来都一样&#xff0c;这个抽象层称为虚拟文件系统(VFS&#xff0c;Virtual Filesystem)。 内核数据结构 Linux内核的V…

【endnote学习】解决为什么文献引用后出现年/月/日格式(endnote对其他类型引用文件的不兼容导致)

为什么文献引用后出现年/月/日格式问题描述问题解决问题描述 在一次文献引用中发现&#xff0c;引用后的文献格式里面多了年/月/日格式&#xff0c;比如选择AIChE格式时&#xff0c;出现&#xff1a; Liu P, Nrskov JK. Kinetics of the Anode Processes in PEM Fuel Cells …

编译原理——基本块、流图、基本块优化、循环优化(代码优化)

一、划分基本块、画流图 找基本块的入口&#xff1a;一共有三类入口&#xff1a;①代码段的第一个指令&#xff1b;②条件跳转和无条件跳转的目标语句&#xff1b;③条件跳转语句的下一条语句&#xff1b;根据划分的入口画流图&#xff0c;一个基本块的区间&#xff1a;从入口…

C++: STL : 容器:set/multi set,map/multimap

一&#xff1a;set容器 1.1: set容器的构造和赋值 简介&#xff1a;所有元素都会在插入时自动被排序 本质&#xff1a;set/multiset 属于关联式容器 &#xff0c;底层结构是二叉树实现 set与multiset区别&#xff1a; set: 不 允许容器中有重复元素 multiset &#xff1a;允…

Shader Graph(一)基本使用

一、如何使用Shader Graph 1.1 新建项目 通过创建URP或HDRP模板项目&#xff0c;可以自动配置好Shader Graph。 1.2 已有项目 在「Package Manager」中安装「Shader Graph」及URP或HDRP组件。然后在弹出的渲染管线向导面板中点击「Fix All」即可。如果项目中存在已经创建好…

three.js实战-Sprite实现标签效果

1. demo效果 2 .什么是精灵(Sprite) 按照Three.js官网的解释是&#xff1a;精灵是一个总是面朝着摄像机的平面&#xff0c;通常含有使用一个半透明的纹理。精灵不会投射任何阴影&#xff0c;即使设置了也将不会有任何效果。 3. 代码大致逻辑 创建一个canvas对象,首先调用ctx…

密码学_DES加密算法

目录 DES&#xff08;Data Encryption Standard&#xff09; IP置换&#xff1a; E盒扩展 S盒压缩 P盒置换 K密钥生成 PC-1置换表&#xff08;通常用此表&#xff09;&#xff1a; PC-2置换表&#xff08;通常用此表&#xff09;&#xff1a; IP-1逆置换表 DES&#x…

MyBatis的SQL执行结果和客户端执行结果不一致问题排查

MyBatis的SQL执行结果和客户端执行结果不一致问题排查问题引入测试表、测试数据问题介绍排查问题调试 MyBatis源码JDBC 执行 SQL解决问题待解决问题最近遇到一个调试很久的问题&#xff0c;MyBatis 查询 Oracle 数据库查询结果与在客户端查询结果不一致。 问题引入 测试表、测…

自动化审批流程有哪些?使用中的优点是什么

自动化审批对HR管理有多重要呢&#xff1f;相信每一位HR都会希望让审批流程实现自动化&#xff0c;从而释放更多的时间去处理更加复杂的工作。 在人力资源管理过程中&#xff0c;自动化审批可以有效帮助HR提高流程效率。当管理人员每天收到很多审批请求之后&#xff0c;如果不…

AVS3中的AMVR和EMVR

在AVS2中运动预测中使用的MV都是1/4像素精度&#xff0c;通过在整像素间插值能显著提升非整像素运动预测的精度&#xff0c;同时带来的问题是随着MV精度的提高编码MVD所需的比特数也会增加。 AMVR AMVR支持的MVD编码5种精度的MVR{1/4,1/2,1,2,4}&#xff0c;索引为0到4&#x…

IU5706 外置MOS、33V输出大功率同步升压芯片产品介绍

概要 IU5706E是高性能宽输入范围&#xff08;4.5V~24V&#xff09;同步升压控制器&#xff0c;支持高达33V的输出电压。输出电压采用恒定频率电流模式脉宽调制&#xff08;PWM&#xff09;控制来实现调节。 芯片通过外部定时电阻器或通过与外部时钟信号同步来设置开关频率。在电…

PGL 系列(五)DeepWalk

DeepWalk 通过随机游走(truncated random walk)学习出一个网络的表示,在网络标注顶点很少的情况也能得到比较好的效果。随机游走起始于选定的节点,然后从当前节点移至随机邻居,并执行一定的步数,该方法大致可分为四个步骤: (a) 展示了原始的用户行为序列。(b) 基于这些用…

Redis架构 - Sentinel哨兵模式

简介 Redis Sentinel是Redis官方提供的一个高可用方案。是一种用于监控、提醒和自动故障转移的系统。它可以监控多个Redis实例&#xff0c;并在主服务器出现故障时执行故障转移&#xff0c;将从服务器升级为主服务器。 在Sentinel模式下&#xff0c;可以设置多个Sentinel实例…

6.1 函数基础

文章目录编写函数调用函数形参和实参函数的形参列表函数的返回类型局部对象自动对象局部静态对象函数声明在头文件中进行函数的声明分离式编译编译和链接多个源文件一个典型的函数 定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的 列表以及函数体。其中&#xff0…

数据库,计算机网络、操作系统刷题笔记20

数据库&#xff0c;计算机网络、操作系统刷题笔记20 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle…

第三十章 数论——扩展中国剩余定理

第三十章 数论——扩展中国剩余定理一、中国剩余定理的弊端二、扩展中国剩余定理1、作用2、内容3、问题4、代码一、中国剩余定理的弊端 在第二十九章中&#xff0c;作者详细地讲解了中国剩余定理的使用&#xff0c;在开始本章节的讲解之前&#xff0c;建议读者先去看上一章节的…

Tensorflow2 图像分类-Flowers数据深度学习模型保存、读取、参数查看和图像预测

目录 1.原文完整代码 1.1 模型运行参数总结 1.2模型训练效果 ​编辑2.模型的保存 3.读取模型model 4.使用模型进行图片预测 5.补充 如何查看保存模型参数 5.1 model_weights 5.2 optimizer_weights 使用之前一篇代码&#xff1a; 原文链接&#xff1a;Tensorflow2 图像分…