【Spring Cloud】黑马头条 用户服务创建、登录功能实现

点击去看上一篇

一、创建用户 model

1.创建用户数据库库 leadnews_user

核心表 ap_user

建库建表语句

这里一定要使用 navicat,执行SQL 文件,以防止 cmd 中的编码问题

先将 SQL 语句,保存在电脑中,再使用 navicat 打开

CREATE DATABASE IF NOT EXISTS leadnews_user DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE leadnews_user;
SET NAMES utf8;
/*
Navicat MySQL Data TransferSource Server         : localhost
Source Server Version : 50721
Source Host           : localhost:3306
Source Database       : leadnews_userTarget Server Type    : MYSQL
Target Server Version : 50721
File Encoding         : 65001Date: 2021-04-12 13:58:42
*/SET FOREIGN_KEY_CHECKS=0;-- ----------------------------
-- Table structure for ap_user
-- ----------------------------
DROP TABLE IF EXISTS `ap_user`;
CREATE TABLE `ap_user` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',`salt` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码、通信等加密盐',`name` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',`password` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码,md5加密',`phone` varchar(11) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号',`image` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像',`sex` tinyint(1) unsigned DEFAULT NULL COMMENT '0 男\r\n            1 女\r\n            2 未知',`is_certification` tinyint(1) unsigned DEFAULT NULL COMMENT '0 未\r\n            1 是',`is_identity_authentication` tinyint(1) DEFAULT NULL COMMENT '是否身份认证',`status` tinyint(1) unsigned DEFAULT NULL COMMENT '0正常\r\n            1锁定',`flag` tinyint(1) unsigned DEFAULT NULL COMMENT '0 普通用户\r\n            1 自媒体人\r\n            2 大V',`created_time` datetime DEFAULT NULL COMMENT '注册时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='APP用户信息表';-- ----------------------------
-- Records of ap_user
-- ----------------------------
INSERT INTO `ap_user` VALUES ('1', 'abc', 'zhangsan', 'abc', '13511223453', null, '1', null, null, '1', '1', '2020-03-19 23:22:07');
INSERT INTO `ap_user` VALUES ('2', 'abc', 'lisi', 'abc', '13511223454', '', '1', null, null, '1', '1', '2020-03-19 23:22:07');
INSERT INTO `ap_user` VALUES ('3', 'sdsa', 'wangwu', 'wangwu', '13511223455', null, null, null, null, null, '1', null);
INSERT INTO `ap_user` VALUES ('4', '123abc', 'admin', '81e158e10201b6d7aee6e35eaf744796', '13511223456', null, '1', null, null, '1', '1', '2020-03-30 16:36:32');
INSERT INTO `ap_user` VALUES ('5', '123', 'suwukong', 'suwukong', '13511223458', null, '1', null, null, '1', '1', '2020-08-01 11:09:57');
INSERT INTO `ap_user` VALUES ('6', null, null, null, null, null, null, null, null, null, null, null);-- ----------------------------
-- Table structure for ap_user_fan
-- ----------------------------
DROP TABLE IF EXISTS `ap_user_fan`;
CREATE TABLE `ap_user_fan` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',`user_id` int(11) unsigned DEFAULT NULL COMMENT '用户ID',`fans_id` int(11) unsigned DEFAULT NULL COMMENT '粉丝ID',`fans_name` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '粉丝昵称',`level` tinyint(1) unsigned DEFAULT NULL COMMENT '粉丝忠实度\r\n            0 正常\r\n            1 潜力股\r\n            2 勇士\r\n            3 铁杆\r\n            4 老铁',`created_time` datetime DEFAULT NULL COMMENT '创建时间',`is_display` tinyint(1) unsigned DEFAULT NULL COMMENT '是否可见我动态',`is_shield_letter` tinyint(1) unsigned DEFAULT NULL COMMENT '是否屏蔽私信',`is_shield_comment` tinyint(1) unsigned DEFAULT NULL COMMENT '是否屏蔽评论',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='APP用户粉丝信息表';-- ----------------------------
-- Records of ap_user_fan
-- ------------------------------ ----------------------------
-- Table structure for ap_user_follow
-- ----------------------------
DROP TABLE IF EXISTS `ap_user_follow`;
CREATE TABLE `ap_user_follow` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',`user_id` int(11) unsigned DEFAULT NULL COMMENT '用户ID',`follow_id` int(11) unsigned DEFAULT NULL COMMENT '关注作者ID',`follow_name` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '粉丝昵称',`level` tinyint(1) unsigned DEFAULT NULL COMMENT '关注度\r\n            0 偶尔感兴趣\r\n            1 一般\r\n            2 经常\r\n            3 高度',`is_notice` tinyint(1) unsigned DEFAULT NULL COMMENT '是否动态通知',`created_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='APP用户关注信息表';-- ----------------------------
-- Records of ap_user_follow
-- ------------------------------ ----------------------------
-- Table structure for ap_user_realname
-- ----------------------------
DROP TABLE IF EXISTS `ap_user_realname`;
CREATE TABLE `ap_user_realname` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',`user_id` int(11) unsigned DEFAULT NULL COMMENT '账号ID',`name` varchar(20) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '用户名称',`idno` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '资源名称',`font_image` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '正面照片',`back_image` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '背面照片',`hold_image` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手持照片',`live_image` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '活体照片',`status` tinyint(1) unsigned DEFAULT NULL COMMENT '状态\r\n            0 创建中\r\n            1 待审核\r\n            2 审核失败\r\n            9 审核通过',`reason` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '拒绝原因',`created_time` datetime DEFAULT NULL COMMENT '创建时间',`submited_time` datetime DEFAULT NULL COMMENT '提交时间',`updated_time` datetime DEFAULT NULL COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='APP实名认证信息表';-- ----------------------------
-- Records of ap_user_realname
-- ----------------------------
INSERT INTO `ap_user_realname` VALUES ('1', '1', 'zhangsan', '512335455602781278', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbHSAQlqFAAXIZNzAq9E126.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbF6AR16RAAZB2e1EsOg460.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbDeAH2qoAAbD_WiUJfk745.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9ba9qANVEdAAS25KJlEVE291.jpg', '9', '', '2019-07-30 14:34:28', '2019-07-30 14:34:30', '2019-07-12 06:48:04');
INSERT INTO `ap_user_realname` VALUES ('2', '2', 'lisi', '512335455602781279', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbHSAQlqFAAXIZNzAq9E126.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbF6AR16RAAZB2e1EsOg460.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbDeAH2qoAAbD_WiUJfk745.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9ba9qANVEdAAS25KJlEVE291.jpg', '1', '', '2019-07-11 17:21:18', '2019-07-11 17:21:20', '2019-07-12 06:48:04');
INSERT INTO `ap_user_realname` VALUES ('3', '3', 'wangwu6666', '512335455602781276', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbHSAQlqFAAXIZNzAq9E126.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbF6AR16RAAZB2e1EsOg460.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbDeAH2qoAAbD_WiUJfk745.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9ba9qANVEdAAS25KJlEVE291.jpg', '9', '', '2019-07-11 17:21:18', '2019-07-11 17:21:20', '2019-07-12 06:48:04');
INSERT INTO `ap_user_realname` VALUES ('5', '5', 'suwukong', '512335455602781279', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbHSAQlqFAAXIZNzAq9E126.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbF6AR16RAAZB2e1EsOg460.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbDeAH2qoAAbD_WiUJfk745.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9ba9qANVEdAAS25KJlEVE291.jpg', '1', '', '2020-08-01 11:10:31', '2020-08-01 11:10:34', '2020-08-01 11:10:36');

2.在 heima-leadnews-model 模块底下创建 com.heima.model.user 包

3.在 com.heima.model.user 包底下创建 pojos 包

dtos 包的创建暂时忽略,不过这里也会讲解 dtos 包的大概作用(存放 DTO 类型)

POJO

POJO 的全程有几种,如 pure old java object 、plain ordinary java object 等。但意思都差不多,可以理解为就是简单的 Java 对象,符合没有被继承类、没有实现接口、属性私有、拥有无参构造方法等规则的 Java 对象

POJO 可划分为 VO、DTO、BO、DO 等

POJO全称作用
VOView Object前端会将 VO 内的属性渲染到页面上
DTOData Transfer Object前后端通过 DTO 传输数据、后端的服务与服务之间也可以通过 DTO 传输数据
BOBusiness ObjectBO 内会封装多个 DO,即业务直接处理 BO,间接处理 DO。如简历相关业务,简历就是 BO,专业技能、实习经历、项目经历、个人信息等为 DO
DOData ObjectDO 中的属性与数据库表中的属性一一对应,即实体类

这里黑马头条中的包划分可能不够严谨,也可能有其他考量,不是重点,暂时不关心

在阿里开发规范中,规定类名中的 DO/BO/DTO/VO/AO/PO/UID 需要大写

4.在 pojos 包中创建 ApUser 类

package com.heima.model.user.pojos;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** <p>* APP用户信息表* </p>** @author itheima*/
@Data
@TableName("ap_user")
public class ApUser implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.AUTO)private Integer id;/*** 密码、通信等加密盐*/@TableField("salt")private String salt;/*** 用户名*/@TableField("name")private String name;/*** 密码,md5加密*/@TableField("password")private String password;/*** 手机号*/@TableField("phone")private String phone;/*** 头像*/@TableField("image")private String image;/*** 0 男1 女2 未知*/@TableField("sex")private Boolean sex;/*** 0 未1 是*/@TableField("is_certification")private Boolean certification;/*** 是否身份认证*/@TableField("is_identity_authentication")private Boolean identityAuthentication;/*** 0正常1锁定*/@TableField("status")private Boolean status;/*** 0 普通用户1 自媒体人2 大V*/@TableField("flag")private Short flag;/*** 注册时间*/@TableField("created_time")private Date createdTime;}
@MyBatisPlus 注解
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

ApUser 类使用了 MybatisPlus 中的 4 个注解

注解作用
@TableName

让 MybatisPlus 识别当前类对应的表,ApUser > ap_user

@TableId让 MybatisPlus 识别当前属性为表的主键,id > id
@IdType让 MybatisPlus 识别当前主键的类型,IdType.AUTO > 自增类型
@TableField让 MybatisPlus 识别当前属性对应的表字段,name > name

添加注解是为了防止数据库字段或 Java 代码属性的命名不规范、不统一

蛇形驼峰转化

如果满足以下三个条件,即可开启蛇形驼峰转化,可以不用上面的 MybatisPlus 注解来进行识别:

  • Java 严格按照驼峰命名(名字中单词首字母大写,如属性名 helloWorld、类名 HelloWorld)
  • 数据库严格按照蛇形命名(名字中的单词之间用下划线分割,如 hello_world)
  • 名字都一一对应完全相同(名字都是 hello world 只是命名形式不同)

在 yml 中开启蛇形驼峰转化:

    黑马提供的初始项目的 model 模块下并没有 resources 目录,需要自己创建。然后在 resources 目录下创建 application.yml 文件来进行配置(黑马并没有使用 MybatisPlus 的自动命名转化配置,为了不给后面埋雷,不建议大家做这个配置)

mybatis-plus:configuration:map-underscore-to-camel-case: true

MyBatisPlus POM 依赖

在 heima-leadnews-model 模块的 pom.xml 中添加依赖


<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId>
</dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

实际上 mybatis-plus-boot-starter 中包含了 mybatis,但是可能和导入的 mybatis 相比版本什么的有点不同,建议按照黑马的来

@Lombok 注解
import lombok.Data;
注解作用
@Data为当前类的所有属性添加 getter & setter 方法

Lombok POM 依赖

该依赖已经在父工程 pom.xml 中的 dependencies 标签下被引入,在该标签下被引入的依赖会在所有子模块中生效,因此无需再次引入

<!-- lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><scope>provided</scope>
</dependency>

二、用户 service 微服务搭建

微服务搭建口诀:建 module、改 pom、写 yml、主启动、业务类(不绝对,仅提供参考)

1.在heima-leadnews-service 模块底下的 pom.xml 中添加所有微服务公用的依赖

黑马提供的初始工程中已经添加好了,不必再次添加

引入其他子模块
<!-- 引入依赖模块 -->
<dependency><groupId>com.heima</groupId><artifactId>heima-leadnews-model</artifactId>
</dependency><dependency><groupId>com.heima</groupId><artifactId>heima-leadnews-common</artifactId>
</dependency><dependency><groupId>com.heima</groupId><artifactId>heima-leadnews-feign-api</artifactId>
</dependency>
依赖作用
heima-leadnews-model导入实体类等,如 ApUser
heima-leadnews-common导入全局配置、全局功能等,如全局异常捕获
heima-leadnews-feign-api导入该模块中统一管理的 feign 远程调用接口(一般微服务之间的调用,都通过 feign 来调用)
引入开发和测试相关依赖
<!-- Spring boot starter -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
依赖作用
spring-boot-starter-web

包含 @Controller、@Service、@RequestMapping 等 web 相关注解,实现相关 web 功能

spring-boot-starter-test包含 @SpringBootTest、@Test 等测试相关注解,实现相关测试功能

SpringBoot 把许多场景抽象为了启动器 starter。当前我们需要一个搭建 web 服务的应用场景,因此使用 starter-web 启动器,即可自动完成 web 服务的所有相关配置(使用默认值),如将使用了 @Bean 注解的类注册进 spring 容器、将内置的 tomcat 配置好等等。与 Spring 相比方便了许多,减少了大量配置文件、配置操作

引入 nacos 服务注册、配置中心相关依赖
<!-- Spring Cloud Alibaba Nacos-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
依赖作用
spring-cloud-starter-alibaba-nacos-discovery

实现 nacos 相关服务发现、服务注册等功能

spring-cloud-starter-alibaba-nacos-config实现 nacos 相关配置拉取功能

注册中心

在微服务架构中,会有某些服务需要由许多机器/容器来提供,而每个机器/容器的 url 都是不一样的。因此我们需要通过服务注册中心将不同的 url 注册到同一个服务名底下,之后再调用该服务名的时候,会从注册中心将服务注册表(包含着 url 与服务名之间映射关系)拉取到本地,然后 nacos 内置的 Ribbon 会将服务名转化为具体的 url(默认轮询,这次是第一个 url,下次就轮到另一个 url),再进行服务调用

配置中心

由于在微服务架构下有许许多多的服务,因此也会产生许许多多的配置文件,每个配置文件也会有许许多多的配置项。当有需求变更时,修改起配置来十分头疼,管理起来也很混乱,很容易改错配置,开发环境直接成为修罗炼狱。而配置中心可以让项目中的配置全部统一管理,并且还可以实现动态修改、运行环境区分(开发、测试、生产等)、配置回滚等功能,很好的解决上述问题

2.在 heima-leadnews-service 模块底下创建 heima-leadnews-user 子模块

黑马提供初始项目中已经建好了的,不必再重建

3.修改 heima-leadnews-user 底下的 pom.xml

由于我们已经在 heima-leadnews-user 的父工程 heima-leadnews-service 中引入了必要的依赖,因此这里暂时不需要添加依赖

<?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"><parent><artifactId>heima-leadnews-service</artifactId><groupId>com.heima</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>heima-leadnews-user</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties></project>

4.编写 heima-leadnews-user 的 yml 配置

在 heima-leadnews-user 底下的 resources 目录中新建 bootstrap.yml 文件

SpringBoot 核心配置文件有两种:bootstrap 和 application

bootstrap

bootstrap 是应用程序的父上下文,由父 ApplicationContext 加载,优先级更高。一般用来做系统层级的参数配置

application

application 是当前应用程序的上下文,优先级较低。一般用来做应用层级的参数配置

为什么用 bootstrap

由于 Spring 根据优先级会先加载 bootstrap 中 nacos 服务的 url(没有就先使用 nacos 默认的),并进行连接尝试,如果你的 nacos 不在默认的 url 上,那么就会报错。因此一定要在 bootstrap 上做好配置,避免报错,才能启动成功

编写 bootstarp 文件的内容
server:port: 51801
spring:application:name: leadnews-usercloud:nacos:discovery:server-addr: localhost:8848config:server-addr: localhost:8848file-extension: ymlprofiles:active: dev

注意使用自己的 nacos 服务地址

nacos 下载 

Releases · alibaba/nacos · GitHub

通过 GitHub 下载,可以自行选择版本(我当时下载的是 1.1.4),点击版本下的 Assets 中的 nacos-server-1.1.4.zip(不论 tar.gz/zip 在 linux 和 windows 都能使用,应该)

https://github.com/alibaba/nacos/releases/download/1.1.4/nacos-server-1.1.4.zip

nacos 启动

解压后双击 bin/startup.cmd 启动 nacos(默认端口为 8848)

在 nocos 配置中心添加 heima-leadnews-user 的服务配置

左边导航栏选择配置列表,点击列表右上方的加号,添加配置

Data ID = spring.application.name (即 leadnews-user) + "-" + spring.profiles.active (即 dev) + "." + spring.cloud.nacos.config.file-extension (即 yml) = leadnews-user-dev.yml

spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: 159357
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:mapper-locations: classpath*:mapper/*.xml# 设置别名包扫描路径,通过该属性可以给包中的类注册别名type-aliases-package: com.heima.model.user.pojos

5.完善 heima-leadnews-user 的主启动类 UserApplication

创建 com.heima.user.UserApplication 主启动类(黑马提供的初始工程也建好了)

package com.heima.user;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.heima.user.mapper")
public class UserApplication {public static void main(String[] args) {SpringApplication.run(UserApplication.class,args);}
}
@SpringBootApplication 注解

用来标记当前类为 SpringBoot 的主配置类,SpringBoot 应用由当前类的 main 方法启动,当 mian 方法执行下面这条代码的时候,

SpringApplication.run(UserApplication.class,args);

SpringBoot 将会把当前包底下的 @Bean 方法返回的对象以及 @Component 类的实例化对象自动存放入 Spring 容器中,并且将所有的配置项以默认值自动装配(约定大于配置)

@EnableDiscoveryClient 注解

该注解是由 SpringBoot 提供的,拥有服务注册与订阅的相关功能。使用该注解后,SpringBoot 应用会自动向配置的服务注册中心注册自己,并定时发送心跳包告知注册中心自己的服务状态

@MapperScan 注解

由 heima-leadnews-model 中的 MyBatis 依赖所提供的注解,可以自动扫描指定包下的类,将扫描到的接口作为 Mapper 并将其实现。有了这个注解可以不必再 XXXMapper 接口上再写 @Mapper 注解了。如果当前服务有许多的 Mapper 接口,那每一个接口上都要写 @Mapper 注解,就会有许多的 @Mapper 注解。如果使用 @MapperScan 注解,即可用一个 @MapperScan 注解代替大量 @Mapper 注解

6.构建 heima-leadnews-user 的微服务目录

config、controller、service、mapper

config

存放配置相关类

controller

为前端提供访问接口,不关心具体业务逻辑,只负责接收参数和返回结果

service

向 contrller 提供业务逻辑接口,serviceImpl 负责将接口中的方法实现

mapper

向 service 提供操作持久层数据的接口,mapper.xml 负责实现接口

为什么这样设计

这种设计方式体现了一种思想 MVC(即 model + view + controller)

分层作用
view从 controller 获取数据,并展示给用户
controller接收 view 的请求,让 model 进行处理,并返回处理结果给 view
model操作数据完成业务,将结果返回给 controller

view 对应项目中的 前端,controller 对应项目中的 controller,model 对应项目中的 service + controller。controller 就如一个中介者,封装 view 和 model 之间的交互,松散耦合,集中控制交互。而 service 与 serviceIpml 分开,mapper 与 mapper.xml 分开,体现了依赖倒转,使调用方不依赖于实现,而是依赖于抽象接口

7.开发 heima-leadnews-user 登录功能相关的 controller 层代码

在 controller 下新建 v1 包,表示第一个版本

在 v1 包底下新建 ApUserLoginController 类

登录接口
接口路径/api/v1/login/login_auth
请求方式POST
参数LoginDto
响应结果ResponseResult

LoginDto

在 heima-leadnews-model 模块下的 com.heima.model.user.dtos 包下创建 LoginDto

@Data
public class LoginDto {/*** 手机号*/private String phone;/*** 密码*/private String password;
}

ResponseResult

在 heima-leadnews-model 中的 com.heima.model.common.dtos 包底下创建 ResponseResult 类


/*** 通用的结果返回类* @param <T>*/
public class ResponseResult<T> implements Serializable {private String host;private Integer code;private String errorMessage;private T data;public ResponseResult() {this.code = 200;}public ResponseResult(Integer code, T data) {this.code = code;this.data = data;}public ResponseResult(Integer code, String msg, T data) {this.code = code;this.errorMessage = msg;this.data = data;}public ResponseResult(Integer code, String msg) {this.code = code;this.errorMessage = msg;}public static ResponseResult errorResult(int code, String msg) {ResponseResult result = new ResponseResult();return result.error(code, msg);}public static ResponseResult okResult(int code, String msg) {ResponseResult result = new ResponseResult();return result.ok(code, null, msg);}public static ResponseResult okResult(Object data) {ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());if(data!=null) {result.setData(data);}return result;}public static ResponseResult errorResult(AppHttpCodeEnum enums){return setAppHttpCodeEnum(enums,enums.getErrorMessage());}public static ResponseResult errorResult(AppHttpCodeEnum enums, String errorMessage){return setAppHttpCodeEnum(enums,errorMessage);}public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){return okResult(enums.getCode(),enums.getErrorMessage());}private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){return okResult(enums.getCode(),errorMessage);}public ResponseResult<?> error(Integer code, String msg) {this.code = code;this.errorMessage = msg;return this;}public ResponseResult<?> ok(Integer code, T data) {this.code = code;this.data = data;return this;}public ResponseResult<?> ok(Integer code, T data, String msg) {this.code = code;this.data = data;this.errorMessage = msg;return this;}public ResponseResult<?> ok(T data) {this.data = data;return this;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getErrorMessage() {return errorMessage;}public void setErrorMessage(String errorMessage) {this.errorMessage = errorMessage;}public T getData() {return data;}public void setData(T data) {this.data = data;}public String getHost() {return host;}public void setHost(String host) {this.host = host;}}

AppHttpCodeEnum

在 heima-leadnews-model 模块中的 com.heima.model.common.enums 包下新建 AppHttpCodeEnum 类

public enum AppHttpCodeEnum {// 成功段固定为200SUCCESS(200,"操作成功"),// 登录段1~50NEED_LOGIN(1,"需要登录后操作"),LOGIN_PASSWORD_ERROR(2,"密码错误"),// TOKEN50~100TOKEN_INVALID(50,"无效的TOKEN"),TOKEN_EXPIRE(51,"TOKEN已过期"),TOKEN_REQUIRE(52,"TOKEN是必须的"),// SIGN验签 100~120SIGN_INVALID(100,"无效的SIGN"),SIG_TIMEOUT(101,"SIGN已过期"),// 参数错误 500~1000PARAM_REQUIRE(500,"缺少参数"),PARAM_INVALID(501,"无效参数"),PARAM_IMAGE_FORMAT_ERROR(502,"图片格式有误"),SERVER_ERROR(503,"服务器内部错误"),// 数据错误 1000~2000DATA_EXIST(1000,"数据已经存在"),AP_USER_DATA_NOT_EXIST(1001,"ApUser数据不存在"),DATA_NOT_EXIST(1002,"数据不存在"),// 数据错误 3000~3500NO_OPERATOR_AUTH(3000,"无权限操作"),NEED_ADMIND(3001,"需要管理员权限");int code;String errorMessage;AppHttpCodeEnum(int code, String errorMessage){this.code = code;this.errorMessage = errorMessage;}public int getCode() {return code;}public String getErrorMessage() {return errorMessage;}
}
ApUserLoginController 代码
@RestController
@RequestMapping("/api/v1/login")
public class ApUserLoginController {@Autowiredprivate ApUserService apUserService;@PostMapping("/login_auth")public ResponseResult login(@RequestBody LoginDto dto){return apUserService.login(dto);}
}

@RestController 注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {@AliasFor(annotation = Controller.class)String value() default "";
}

本质是个复合注解,即 @RestController = @Controller + @ResponseBody

注解作用
@Target

规定注解的使用范围(

ElementType.TYPE 类注解,

ElementType.FIELD 属性注解,

ElementType.METHOD 方法注解...

@Retention

规定注释的有效时期(

RetentionPolicy.SOUTCE 代码注解,

RetenionPolicy.CLASS 字节码注解,

RetentionPolicy.RUNTIME 运行注解...

@Documented

将该注解写入 JAVADOC 中

@Inherited使用该注解的类的子类,可以继承该注解
注解作用
@Controller包含 @Component 注解,可以将使用该注解的类的实例化对象注册到 Spring IOC 容器中
@ResponseBody将方法的返回值直接写入 HTTP Response 的 body 中

@RequestMapping 注解

当请求路径与 @RequestMapping 的 value 值一致时,对请求进行路由,路由到当前类或方法

@AutoWired 注解

自动装载,将注册进容器中的 ApUserService 类的实例化对象取出并注入到当前的 ApUserController 类中。为 ApUserController 提供业务方法(ApUserService 还没建好,可以先不注入)

@RequestBody 与 @RequestParam 的区别

@RequestBody 发送

@RequestBody 接收

@RestController
public class TestController {@RequestMapping(value = "/test")public void test(@RequestBody String body) {System.out.println(body);}
}

@RequestParam 发送

第一种 query params

第二种 body form-data

@RequestParam 接收

@RestController
public class TestController {@RequestMapping(value = "/test")public void test(@RequestParam String param) {System.out.println(param);}
}

8.开发 heima-leadnews-user 登录功能相关的 service 层代码

在 service 下新建 apUserService 接口

public interface ApUserService extends IService<ApUser> {/*** app端登录功能* @param dto* @return*/public ResponseResult login(LoginDto dto);
}

IService 是由 MyBatis Plus 提供的一个接口,MyBatis 也提供了对应的实现类 ServiceImpl

ServiceImpl 类注入了 baseMapper 接口,而 MyBatis Plus 也实现了该接口

因此我们可以使用 ServiceImpl 中的丰富的基础数据处理方法间接调用 mapper 来进行数据库操作

在 service 下新建 impl 包并创建 ApUserServiceImpl 类实现 ApUserService 接口

@Service
@Transactional
@Slf4j
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {/*** app端登录功能* @param dto* @return*/@Overridepublic ResponseResult login(LoginDto dto) {//1.正常登录 用户名和密码if(StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())){//1.1 根据手机号查询用户信息ApUser dbUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));if(dbUser == null){return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");}//1.2 比对密码String salt = dbUser.getSalt();String password = dto.getPassword();String pswd = DigestUtils.md5DigestAsHex((password + salt).getBytes());if(!pswd.equals(dbUser.getPassword())){return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);}//1.3 返回数据  jwt  userString token = AppJwtUtil.getToken(dbUser.getId().longValue());Map<String,Object> map = new HashMap<>();map.put("token",token);dbUser.setSalt("");dbUser.setPassword("");map.put("user",dbUser);return ResponseResult.okResult(map);}else {//2.游客登录Map<String,Object> map = new HashMap<>();map.put("token",AppJwtUtil.getToken(0L));return ResponseResult.okResult(map);}}
}
判断用户名和密码是否为空

使用 StringUtils 的 isNotBlank 方法判断字符串是否为空、是否有长度、是否含有空白字符

if(StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())) {// 1.正常登录
} else {// 2.游客登录Map<String,Object> map = new HashMap<>();map.put("token",AppJwtUtil.getToken(0L));return ResponseResult.okResult(map);
}

游客登陆返回

{"host": "","code": 200,    // AppHttpCodeEnum.SUCCESS"errorMessage": "操作成功","data": {"token": // 使用 AppJwtUtil.getToken(0L) 方法生成游客 token,具体实现下面会讲}
}
判断该用户是否存在

使用 ServiceImpl 中的 getOne 方法,用 Wrapper 构造查询条件( dbUser.getPhone() 等与 dto.getPhone)

//1.1 根据手机号查询用户信息
ApUser dbUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
if(dbUser == null){return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");
}

不存在返回

{"host": "","code": 1002,    // AppHttpCodeEnum.DATA_NOT_EXIST"errorMessage": "用户信息不存在","data": {}
}
判断密码是否正确

数据库中的密码加密是通过先加盐再MD5的方式完成的

MD5 为一种单向加密算法,只能加密不能解密。因此很适合用来加密密码,这样连数据库所有者都无法知道用户的真实密码,大大提高安全性。但是既然这么安全为什么还要加盐呢?因为MD5的广泛使用,黑客认为有利可图,于是使用暴力学习,将许多密码进行 MD5 加密,并将映射关系存起来,这样就能用映射表,将 MD5 加密后的密码转为真实的密码。所以,我们通过加盐,也就是给原有的密码加上其他的字符/字符串再进行 MD5 加密,使得暴力解密后的密码也不是真正的密码

使用 org.springframework.util 中的 DigestUtils 类里的 md5DigestAsHex 方法对 用户输入的密码 + 数据库中获取的盐值 进行加密,然后将加密后的输入密码与数据库中的比较

//1.2 比对密码
String salt = dbUser.getSalt();
String password = dto.getPassword();
String pswd = DigestUtils.md5DigestAsHex((password + salt).getBytes());
if(!pswd.equals(dbUser.getPassword())){return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
}

密码错误返回

{"host": "","code": 2,    // AppHttpCodeEnum.LOGIN_PASSWORD_ERROR"errorMessage": "密码错误","data": {"token": // 使用 AppJwtUtil.getToken(0L) 方法生成游客 token,具体实现下面会讲}
}
生成 JWT

JWT,即 Json Web Token。它由三部分组成

header

JWT头

一个 JSON 对象用来描述 JWT 的签名算法、令牌类型

payload

JWT载荷

一个 JSON 对象用来传递数据

signature

JWT签名

使用 base64 加密后的 header + base64 加密后的 payload + 服务器生成的密钥,通过指定算法生成签名

形如 headerheaderheader.payloadpayloadpayload.signaturesignaturesignature

即 header 与 payload 与 signature 之间使用 '.' 点号隔开

eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQrDMAzA_uJzA_biuKa_cWnKUigEnMLG2N_nHnaTEPrAMRosUFhUmCmZUEmsImnN-kgZ90q2Z1EWmKDZgIVmRBRS1gn8WuP2t4963t099FnbaWF2bWHWe3B99f9Z-D5bNPz-AKKQJrqAAAAA.0ZNpu7dGgNtoF-UBGM3LvFZxEsltZFHazfByij_IW5KIv82oNxV4VremFE4zAx_lFAxE2XuOZ53LGb1u2mY7Lg

由于 base64 可以解密,因此不要在 payload 中存放重要的隐私信息

为什么需要 JWT

JWT 用来存储客户端与服务器之间的身份识别信息,相当于是存会话信息。既然是会话,为什么不用 session 呢?因为 session 依赖于 cookie,session 中会存放用户信息,浏览器的 cookie 中会存 session 的 id,每次浏览器发送请求的时候就会携带 cookie,这样也能将 cookie 中的 sessionID 发送给服务器,这样服务器就能识别出这个请求时哪个用户发出的。可是有些客户端并没有 cookie,因此也无法存储 sessionID,于是我们就使用 JWT,客户端发送请求的时候带上 JWT,而 JWT 中又有用户的信息,这样服务器也能识别出请求的来源。抛弃掉 session cookie,实现前后端分离

在 heima-leadnews-utils 模块下创建 com.heima.utils.common.AppJwtUtil 类

public class AppJwtUtil {// TOKEN的有效期一天(S)private static final int TOKEN_TIME_OUT = 3_600;// 加密KEYprivate static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";// 最小刷新间隔(S)private static final int REFRESH_TIME = 300;// 生产IDpublic static String getToken(Long id){Map<String, Object> claimMaps = new HashMap<>();claimMaps.put("id",id);long currentTime = System.currentTimeMillis();return Jwts.builder().setId(UUID.randomUUID().toString()).setIssuedAt(new Date(currentTime))  //签发时间.setSubject("system")  //说明.setIssuer("heima") //签发者信息.setAudience("app")  //接收用户.compressWith(CompressionCodecs.GZIP)  //数据压缩方式.signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳.addClaims(claimMaps) //cla信息.compact();}/*** 获取token中的claims信息** @param token* @return*/private static Jws<Claims> getJws(String token) {return Jwts.parser().setSigningKey(generalKey()).parseClaimsJws(token);}/*** 获取payload body信息** @param token* @return*/public static Claims getClaimsBody(String token) {try {return getJws(token).getBody();}catch (ExpiredJwtException e){return null;}}/*** 获取hearder body信息** @param token* @return*/public static JwsHeader getHeaderBody(String token) {return getJws(token).getHeader();}/*** 是否过期** @param claims* @return -1:有效,0:有效,1:过期,2:过期*/public static int verifyToken(Claims claims) {if(claims==null){return 1;}try {claims.getExpiration().before(new Date());// 需要自动刷新TOKENif((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){return -1;}else {return 0;}} catch (ExpiredJwtException ex) {return 1;}catch (Exception e){return 2;}}/*** 由字符串生成加密key** @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}}

生成过程

//1.3 返回数据  jwt  user
String token = AppJwtUtil.getToken(dbUser.getId().longValue());
Map<String,Object> map = new HashMap<>();
map.put("token",token);
dbUser.setSalt("");
dbUser.setPassword("");
map.put("user",dbUser);return ResponseResult.okResult(map);
public static String getToken(Long id){Map<String, Object> claimMaps = new HashMap<>();claimMaps.put("id",id);long currentTime = System.currentTimeMillis();return Jwts.builder().setId(UUID.randomUUID().toString()).setIssuedAt(new Date(currentTime))  //签发时间.setSubject("system")  //说明.setIssuer("heima") //签发者信息.setAudience("app")  //接收用户.compressWith(CompressionCodecs.GZIP)  //数据压缩方式.signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳.addClaims(claimMaps) //cla信息.compact();
}

使用 jjwt 依赖提供的 jsonwebtoken 包中的 Jwts.builder 方法构建 jwt,并将用户 id 存放在 jwt 的 payload 中,从而让服务器能够识别请求的发送者

三、测试接口

{"host": null,"code": 200,"errorMessage": "操作成功","data": {"user": {"id": 4,"salt": "","name": "admin","password": "","phone": "13511223456","image": null,"sex": true,"certification": null,"identityAuthentication": null,"status": true,"flag": 1,"createdTime": "2020-03-30T08:36:32.000+00:00"},"token": "eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWL0QrDIAwA_yXPFZLqau3fpBqZg4IQCxuj_770YW93HPeF12iwAfqEiWRxNefiAu-rYx-z48dMNZKUtSaYoPGAjSIizcHjMoGeu9360SHH3VVNn9IONuOzmHHvxvLu_zOm-2zWwvUD7al0KoAAAAA.5GEoiqO9MH7WCZeBWM95XAlAtlrkHvrbzUqZOO0HktuJjcCCJ20MVprJjXXa-DuNY9qdHIy0yt7Z1ziaflTHUw"}
}

基础用户登录功能完成

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

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

相关文章

华为eNSP综合实验考试

VLAN信息表 设备名称 端口 链路类型 VLAN 参数 HZ-HZCampus-Agg01-S5731 GE0/0/1 Trunk PVID:1 Allow-pass&#xff1a;10 20 Eth-trunk1&#xff08;GE0/0/2,0/0/3,0/0/23&#xff09; Trunk PVID:1 Allow-pass&#xff1a;10 20 GE0/0/24 Access PVID&#xf…

(免费)双相情感障碍筛查MDQ 在线测试双向情感障碍

MDQ用于筛查双相障碍&#xff0c;主要包含13个关于双相障碍症状的是非问题&#xff0c;当前测试采用的量表为2010年杨海晨博士翻译版。该量表为目前世界范围内最常用的双相障碍筛查量表&#xff0c;目前在精神科门诊最为常用的量表之一。 双向情感障碍筛查量表&#xff0c;也叫…

【linux】查看CPU的使用率

命令1&#xff1a;top top 总体系统信息 uptime&#xff1a;系统的运行时间和平均负载。tasks&#xff1a;当前运行的进程和线程数目。CPU&#xff1a;总体 CPU 使用率和各个核心的使用情况。内存&#xff08;Memory&#xff09;&#xff1a;总体内存使用情况、可用内存和缓存…

我记不住的getopt_long的那些参数和返回值

前言&#xff1a;最近在学习面向Linux系统进行C语言的编程&#xff0c;通过查询man手册和查看网络上的各种文章来获取一点点的知识&#xff0c;重点是看完手册还是一脸懵逼&#xff0c;搞不懂手册里面再说啥&#xff0c;而本篇文章将记录一下学习getopt_long的那些参数和返回值…

adb手机调试常用命令

查看手机型号 adb shell getprop ro.product.model 查看电池状况 adb shell dumpsys battery 查看分辨率 adb shell wm size 查看屏幕密度 adb shell wm density 查看显示屏参数 adb shell dumpsys window displays 查看android_id adb shell settings get secure android…

Elasticsearch:运用向量搜索通过图像搜索找到你的小狗

作者&#xff1a;ALEX SALGADO 你是否曾经遇到过这样的情况&#xff1a;你在街上发现了一只丢失的小狗&#xff0c;但不知道它是否有主人&#xff1f; 了解如何使用向量搜索或图像搜索来做到这一点。 通过图像搜索找到你的小狗 您是否曾经遇到过这样的情况&#xff1a;你在街…

V10服务器安装virt-manage

kvm是什么 KVM(Kernel-based Virtual Machine, 即内核级虚拟机) 是一个开源的系统虚拟化模块。它使用Linux自身的调度器进行管理&#xff0c;所以相对于Xen&#xff0c;其核心源码很少。目前KVM已成为学术界的主流VMM之一&#xff0c;它包含一个为处理器提供底层虚拟化 可加载…

概率论和数理统计(三)数理统计基本概念

前言 “概率论”是给定一个随机变量X的分布F(x),然后求某事件A概率 P ( x ∈ A ) P(x \in A) P(x∈A)或者随机变量X的数字特征.“统计”是已知一组样本数据 { x 1 , x 2 , . . . x n } \{x_1,x_2,...x_n\} {x1​,x2​,...xn​},去求分布F(x) 统计的基本概念 在统计中&#x…

Vue3-admin-template 框架实现表单身份证获取到 出生年月、性别

一. 首先需效验输入身份证信息是否正确&#xff1a; const sfzhChange () > {// 效验身份证号格式const reg /^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; }; 二.绑定输入框 input 事件&#xff1a; <el-form-item label&q…

监控直流防雷浪涌保护器综合方案

监控系统是一种广泛应用于安防、交通、工业、军事等领域的信息系统&#xff0c;它通过摄像机、传输线路、监控中心等设备&#xff0c;实现对目标区域的实时监视和控制。然而&#xff0c;监控系统也面临着雷电的威胁&#xff0c;雷电可能通过直击雷、感应雷、雷电波侵入等途径&a…

Linux服务器安装Dotnet8

1. 下载dotnet8 sdk 下载 .NET 8.0 SDK (v8.0.100) - Linux x64 Binaries 拿到 dotnet-sdk-8.0.100-linux-x64.tar.gz 文件 2. 把文件上传到 /usr/local/software 目录 mkdir -p /usr/local/software/dotnet8 把文件拷贝过去 mv dotnet-sdk-8.0.100-linux-x64.tar.gz /usr/loc…

安装SSL证书有什么意义?

在当今的数字化时代&#xff0c;网络安全已经成为了一个重要的议题。为了保护网站和用户数据的安全&#xff0c;许多网站都选择了安装SSL证书。同时&#xff0c;很多用户不明白安装SSL证书到底有什么意义&#xff1f; 一、网站实现加密传输 用户通过http协议访问网站时&#x…

vue使用navigator.mediaDevices.getUserMedia调用相机功能

目录 前言&#xff1a; API&#xff1a; API简单示例&#xff1a; 拍照功能 实现效果&#xff1a; 前言&#xff1a; 本文将介绍Vue中如何使用navigator.mediaDevices.getUserMedia调用相机功能&#xff0c;实现拍照使用实例&#xff0c;需要的朋友可以参考一下。 注意…

深度学习YOLOv5车辆颜色识别检测 - python opencv 计算机竞赛

文章目录 1 前言2 实现效果3 CNN卷积神经网络4 Yolov56 数据集处理及模型训练5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习YOLOv5车辆颜色识别检测 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0…

linux高级篇基础理论一(详细文档、Apache,网站,MySQL、MySQL备份工具)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️不能因为人生的道路坎坷,就使自己的身躯变得弯曲;不能因为生活的历程漫长,就使求索的 脚步迟缓。 ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xff1a;云计算技…

VS2022升级之后,原有项目出现异常

最近对VS2022做了升级&#xff0c;发现之前开发的WebApi&#xff08;使用Net5&#xff09;调试运行报错&#xff1a; 根据提示的错误信息也在网上查找了一些资料&#xff0c;均无法正常解决&#xff0c;偶然发现问题是因为VS2022升级之后&#xff0c;不再支持Net5&#xff0c;…

【开源】基于Vue和SpringBoot的固始鹅块销售系统

项目编号&#xff1a; S 060 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S060&#xff0c;文末获取源码。} 项目编号&#xff1a;S060&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 鹅块类型模块2.3 固…

论文技巧2

目录 1 找基准模型2 找模块小论文的三个实验怎么做对比试验Sota的挑选对⽐论⽂结果的获取3 消融实验什么是消融实验怎么做消融实验4 实例分析怎么做实例分析小论文必备三张图1 找基准模型 2 找模块 小论文的三个实验 怎么做对比试验

高性能音乐流媒体服务Diosic

什么是 Diosic ? Diosic 是一个开源的基于网络的音乐收集服务器和流媒体。主要适合需要部署在硬件规格不高的服务器上的用户。Diosic 是使用 Rust 开发的&#xff0c;具有低内存使用率和高性能以及用于流媒体音乐的非常干净的界面。 安装 在群晖上以 Docker 方式安装。 在注…

基于JavaWeb+SpringBoot+Vue医疗器械商城微信小程序系统的设计和实现

基于JavaWebSpringBootVue医疗器械商城微信小程序系统的设计和实现 源码获取入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 前言 摘 要 目前医疗器械行业作为医药行业的一个分支&#xff0c;发展十分迅速。…