SpringBoot第 17 讲:SpringBoot+JWT

关于JWT的讲解请参考:SpringCloud第14讲:(番外篇)JWT

一、项目演示

没有登陆直接请求列表接口,系统会要求先进行登录
在这里插入图片描述

登录成功后请求列表接口,可以正常响应数据

在这里插入图片描述

二、后台开发

2.1、pom.xml

添加redis、jwt坐标

<!--jwt坐标-->
<dependency>
	<groupId>com.nimbusds</groupId>
	<artifactId>nimbus-jose-jwt</artifactId>
	<version>9.11.1</version>
</dependency>

2.2、application.yml

配置redis

server:
  port: 8070
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.8:3306/test_plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    username: root
    password: Aa123123.
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
  redis:
    port: 6379 # Redis服务器连接端口
    host: 127.0.0.1 # Redis服务器地址
    database: 0 # Redis数据库索引(默认为0)
    password: # Redis服务器连接密码(默认为空)
    timeout: 5000ms # 连接超时时间(毫秒)
    jedis:
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8 # 连接池中的最大空闲连接
        min-idle: 0 # 连接池中的最小空闲连接
mybatis-plus:
  type-aliases-package: demo.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      table-prefix: t_
      id-type: auto
  mapper-locations: classpath:mappers/*.xml

2.3、RedisConfig

解决redisTemplate保存数据出现乱码问题

package demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        //创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //设置连接工厂
        template.setConnectionFactory(connectionFactory);
        //创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        //设置value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashKeySerializer(jsonRedisSerializer);
        //返回
        return template;
    }
}

2.4、实体层开发

package demo.entity;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String userName;
    private String passwd;
}
package demo.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

@Data
public class Article {
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;
    private String title;
    private String logo;
    private String descn;
    private Date createTime;
    private Long cid;
}

2.5、数据库访问层开发

UserMapper

package demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import demo.entity.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper extends BaseMapper<User> {
}

ArticleMapper

package demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import demo.entity.Article;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface ArticleMapper extends BaseMapper<Article> {
}

2.6、控制层

package demo.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import demo.entity.Article;
import demo.mapper.ArticleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ArticleController {

    @Autowired
    private ArticleMapper articleMapper;

    @GetMapping("/list")
    public Page<Article> findAll(Long pageIndex, Long pageSize){
        Page<Article> page = articleMapper.selectPage(new Page(pageIndex, pageSize), null);
        return page;
    }
}

在登陆成功之后,生成token并保存到redis

package demo.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import demo.entity.User;
import demo.mapper.UserMapper;
import demo.utils.Cast;
import demo.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired(required = false)
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping("/login")
    public Object login(@RequestBody User vo){
        String token = null;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .eq("user_name", vo.getUserName())
                .eq("passwd", vo.getPasswd());
        User usr = userMapper.selectOne(queryWrapper);
        if(usr != null){
            try {
                //登陆成功,生成token保存到reids中
                token = jwtUtils.createJwtToken(usr.getUserName());
                redisTemplate.boundValueOps(Cast.JWT_TOKEN_PREFIX+usr.getUserName()).set(token, 30, TimeUnit.MINUTES);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return token;
    }
}

2.7、工具类

公有常量类

package demo.utils;

/**
 * 公有常量
 */
public class Cast {
    public static final String JWT_HEADER_KEY = "x-jwt-token"; //请求头中的jwt
    public static final String JWT_TOKEN_PREFIX = "login:token:"; //保存在redis中用于校验jwt的key的前缀
}

Jwt工具类

package demo.utils;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * jwt工具类
 */
@Component
public class JwtUtils {
    //使用uuid生成密钥
    private  final String secret= UUID.randomUUID().toString();
    //用户数据的key
    private  final String usernameKey="usernameKey";

    /**
     * 生成token
     * @param username 用户名
     * @return
     */
    public  String createJwtToken(String username) throws Exception {
        //创建头部对象
        JWSHeader jwsHeader =
                new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                        .type(JOSEObjectType.JWT) // 静态常量
                        .build();

        //创建载荷
        Map<String,Object> map=new HashMap<String,Object>();
        map.put(usernameKey, username);
        Payload payload= new Payload(map);

        //创建签名器
        JWSSigner jwsSigner = new MACSigner(secret);//密钥

        //创建签名
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷
        jwsObject.sign(jwsSigner);//再+签名部分


        //生成token字符串
        return jwsObject.serialize();
    }

    /**
     * 验证jwt token是否合法
     * @param jwtStr
     * @return
     */
    @SneakyThrows
    public  boolean verify(String jwtStr) {
        JWSObject jwsObject=JWSObject.parse(jwtStr);
        JWSVerifier jwsVerifier=new MACVerifier(secret);
        return jwsObject.verify(jwsVerifier);
    }

    /**
     * 从token中解析出用户名
     * @param jwtStr
     * @return
     */
    @SneakyThrows
    public String getUserNameFormJwt(String jwtStr){
        JWSObject jwsObject=JWSObject.parse(jwtStr);
        Map<String,Object> map=jwsObject.getPayload().toJSONObject();
        return (String) map.get(usernameKey);
    }
}

2.8、拦截器

在拦截器中拦截HttpRequest请求,执行以下业务逻辑

  • 1、判断请求头中是否携带token
  • 2、判断token是否有效
  • 3、判断token中保存的用户信息是否有效
  • 4、判断请求头的token和redis中的token是否一致
  • 5、请求拦截或token续期
package demo.interceptor;

import demo.entity.User;
import demo.mapper.UserMapper;
import demo.utils.Cast;
import demo.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

@Configuration
@Slf4j
public class MyInterceptor extends WebMvcConfigurerAdapter {

    @Autowired(required = false)
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private JwtUtils jwtUtils;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        HandlerInterceptor handlerInterceptor = new HandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                boolean isOk = false;
                String uri = request.getRequestURI();
                //静态资源放行
                if(uri.contains("js") || uri.contains("html") || uri.contains("css") || uri.contains("login") || uri.equals("/favicon.ico") || uri.equals("/error")){
                    isOk = true;
                } else { //用户鉴权
                    String token = request.getHeader(Cast.JWT_HEADER_KEY);
                    //验证请求头中的token是否合法
                    if(token != null && jwtUtils.verify(token)){
                        // token合法则取出token中保存的账号
                        String account = jwtUtils.getUserNameFormJwt(token);

                        //根据账号验证redis中保存的token是否存在
                        if(redisTemplate.hasKey(Cast.JWT_TOKEN_PREFIX+account)){

                            String jwtToken = (String) redisTemplate.opsForValue().get(Cast.JWT_TOKEN_PREFIX+account);
                            //判断是否是同一个token
                            if(token.equals(jwtToken)){
                                //对token进行续期
                                redisTemplate.opsForValue().set(Cast.JWT_TOKEN_PREFIX+account, jwtToken, 30, TimeUnit.MINUTES);
                                isOk = true;
                            }
                        }
                    }
                    if(!isOk){
                        response.setContentType("application/json;charset=utf8");
                        PrintWriter writer = response.getWriter();
                        writer.print("{\"code\":403, \"msg\":\"没有访问权限\"}");
                        isOk = false;
                    }
                }

                return isOk;
            }
        };
        registry.addInterceptor(handlerInterceptor);
    }
}

三、前端开发

3.1、登陆页面login.html

登陆成功后将token保存到localStorage中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="assets/bootstrap-3.3.7-dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="assets/jquery-3.5.1.min.js"></script>
    <script src="assets/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
    <script src="assets/vue.min-v2.5.16.js"></script>
    <script src="assets/axios.min.js"></script>
</head>
<body>
    <div class="container" id="app">
        <div class="row">
            <div class="col-md-4 col-md-push-4">
                <h3 style="margin-top: 30px;">用户登录</h3>
                <div style="margin-top: 30px;">
                    <label for="uname">用户名:</label>
                    <input id="uname" class="form-control" type="text" v-model="userName">
                </div>
                <div style="margin-top: 15px;">
                    <label for="upasswd">密码:</label>
                    <input id="upasswd" class="form-control" type="password" v-model="passwd">
                </div>
                <div style="margin-top: 15px;">
                    <button class="btn btn-primary" @click="doLogin">登录</button>
                </div>
            </div>
        </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                userName: null,
                passwd: null
            },
            methods: {
                doLogin(){
                    axios.post("/user/login", {
                        userName: this.userName,
                        passwd: this.passwd
                    }).then(res => {
                        console.log(res.data)
                        let token = res.data;
                        if(token == false){
                            alert("用户名或密码错误!");
                        } else {
                            localStorage.setItem("token", res.data)
                            window.location.href = "index.html"
                        }
                    });
                }
            }
        });
    </script>
</body>
</html>

3.2、新闻列表页index.html

在列表页请求后台接口,需要在header中携带token

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="assets/bootstrap-3.3.7-dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="assets/jquery-3.5.1.min.js"></script>
    <script src="assets/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
    <script src="assets/vue.min-v2.5.16.js"></script>
    <script src="assets/axios.min.js"></script>
</head>
<body>
    <div class="container" id="app">
        <table class="table table-striped">
            <caption>文章列表</caption>
            <thead>
            <tr>
                <th align="center">编号</th>
                <th align="center">标题</th>
                <th align="center">描述</th>
                <th align="center">发布时间</th>
                <th align="center">操作</th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="art in articleList">
                <td>{{art.id}}</td>
                <td>{{art.title}}</td>
                <td>{{art.descn}}</td>
                <td>{{art.createTime}}</td>
                <td align="center">
                    <button class="btn btn-link" style="margin-right: 10px;">修改</button>
                    <button class="btn btn-link">删除</button>
                </td>
            </tr>
            </tbody>
        </table>
        <ul class="pagination" v-for="p in pageCnt">
            <li v-if="p == pageIndex" class="active"><a href="#" @click="doGo(p)">{{p}}</a></li>
            <li v-else="p == pageIndex"><a href="#" @click="doGo(p)">{{p}}</a></li>
        </ul>
    </div>

    <script>
        new Vue({
            el: '#app',
            data: {
                articleList: null,
                //用于分页
                pageIndex: 1, //页码
                pageSize: 3, //每页显示的条数
                pageTotal: 0, //总条数
                pageCnt: 0 //总页数
            },
            methods: {
                requestArticleList(){
                    axios.get("/list", {
                            params: {
                                pageIndex: this.pageIndex,
                                pageSize: this.pageSize
                            },
                            headers: {'x-jwt-token': localStorage.getItem("token")}
                        }
                    ).then(res => {
                        console.log(res.data)
                        if(res.data.code == 403){
                            alert("请先登录");
                            window.location.href = "login.html"
                        } else {
                            this.articleList = res.data.records
                            this.pageCnt = res.data.pages
                            this.pageTotal = res.data.total
                            this.pageIndex = res.data.current
                            this.pageSize = res.data.size
                        }
                    })
                },
                doGo(p){
                    this.pageIndex = p
                    this.requestArticleList()
                }
            },
            created: function () {
                this.requestArticleList()
            }
        })
    </script>
</body>
</html>

四、附录

4.1、完整pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>page-helper-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>page-helper-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- Spring&SpringMVC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
        <!-- mysql-connector -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.32</version>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--jwt坐标-->
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.11.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网口通讯与串口通讯

目录 一、简介以及数据格式&#xff1a; 二、网口通讯与串口通讯主要区别&#xff1a; 三、工具小助手&#xff1a; 一、简介以及数据格式&#xff1a; 网口通讯&#xff08;Ethernet&#xff09;和串口通讯&#xff08;Serial&#xff09;都是用于数据传输的通信协议。 1、…

Linux基础——DNS服务器原理及搭建

Linux基础——DNS服务器原理及搭建 一、DNS服务器原理1.DNS系统分布式数据结构2.DNS查询类型3.DNS服物器类型 二、搭建DNS域名解析服务器步骤1.安装bind软件包2. 查看需要修改的配置文件所在路径3. 修改主配置文件4. 修改区域配置文件&#xff0c;添加正向区域配置5.配置正向区…

iptables表、链、规则

netfilter/iptables&#xff08;也就是常说的iptables&#xff09;组成Linux平台下的包过滤防火墙&#xff0c;具有完成封包过滤、封包重定向和网络地址转换&#xff08;NAT&#xff09;等功能。 netfilter是Linux 核心中一个通用架构&#xff0c;它提供了一系列的"表&quo…

POLARDB 从一个使用者的角度来说说,POALRDB 怎么打败 MYSQL RDS

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

【云原生|Docker】14-Dokcer Harbor高可用部署

【云原生Docker】14-Dokcer Harbor高可用部署 文章目录 【云原生Docker】14-Dokcer Harbor高可用部署前言Harbor高可用方案单主复制双主复制多Harbor共享后端存储 Harbor高可用部署方案说明环境说明部署步骤安装nfs安装redis和PostgreSQL安装harbor配置nginx访问测试 总结 前言…

Docker harbor私有仓库部署与管理

目录 1.Docker搭建本地私有仓库 1.首先下载 registry 镜像 2.在 daemon.json 文件中添加私有镜像仓库地址 3. 运行 registry 容器 4.为镜像打标签 5.上传到私有仓库 6.列出私有仓库的所有镜像 7.列出私有仓库的centos镜像有哪些tag 8.先删除原有的centos的镜像&#xf…

TCP协议的相关特性(续)

TCP协议的相关特性 &#x1f50e;滑动窗口&#x1f50e;流量控制&#x1f50e;拥塞控制&#x1f50e;延时应答&#x1f50e;捎带应答&#x1f50e;面向字节流(粘包问题)&#x1f50e;异常情况&#x1f50e;总结 关于 确认应答 超时重传, 连接管理 请参考: 点击这里 &#x1f5…

【场景生成与削减】基于蒙特卡洛法场景生成及启发式同步回带削减风电、光伏、负荷研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

无人机遥感影像应用

目录 一、无人机遥感技术 二、无人机遥感影像数据生产 三、无人机遥感影像应用 一、无人机遥感技术 1.无人机遥感系统组成 1.1无人机遥感系统组成—无人机平台 1.2无人机遥感系统组成—传感器 2.无人机遥感技术的特点 高时效性&#xff1a;准确并快速获取地表数据 高分辨率…

[强化学习]学习路线和关键词拾零

强化学习学习方法和路线 学习路线 先从基础教材开始&#xff0c;构建RL的知识框架&#xff0c;熟悉关键名词和公式推导&#xff0c;扩展到Model-Free的Value-Based和Policy-Based方法&#xff0c;同时参考github的代码练习。接下来精读几篇经典论文&#xff0c;如DQN,PPO等。…

Python高光谱遥感数据处理与机器学习

Python高光谱遥感数据处理与机器学习 第一章、高光谱基础 高光谱遥感简介 什么是高光谱遥感&#xff1f; 高光谱遥感为什么重要&#xff1f; 高光谱遥感与其他遥感技术的区别是什么&#xff1f; 高光谱遥感的历史和发展 高光谱传感器与数据获取 高光谱传感器类型 如何获…

ai改写句子软件-ai改写

AI免费伪原创&#xff1a;助力网站内容升级 您是否曾经为网站优化而烦恼&#xff0c;无论是内容更新还是SEO优化&#xff0c;都需要大量的时间和精力。但是&#xff0c;您是否知道&#xff0c;现在有一款能够使用AI技术来帮助您完成这些任务&#xff0c;而且还是免费的呢&…

瑞吉外卖-项目笔记

文章目录 1.业务开发day011.软件开发整体介绍2.项目整体介绍:star:3.开发环境搭建4.登录功能&#xff1a;star4.1代码实现 5.退出功能6.页面效果出现 day021.完善登录功能2.新增员工功能 1.业务开发 day01 1.软件开发整体介绍 2.项目整体介绍⭐️ 后端&#xff1a;管理菜品和…

基于web的病号康复训练系统asp.net+sqlserver+C#

本系统主要内容分为病号管理模块,康复师管理模块,管理员管理模块等三大模块 1,病号管理模块主要分为:用户管理,在线问答,在线预约,用户中心,信息查询. 2. 康复师管理模块主要有:康复师信息管理,病人信息管理&#xff0c;预约信息管理&#xff0c;留言信息管理&#xff0c;训练计…

< elementUi组件封装: 通过 el-tag、el-popover、vue动画等实现公告轮播 >

文章目录 &#x1f449; 前言&#x1f449; 一、效果演示&#x1f449; 二、实现思路&#x1f449; 三、实现案例往期内容 &#x1f4a8; &#x1f449; 前言 在 Vue elementUi 开发中&#xff0c;遇到这么一个需求&#xff0c;要实现公告轮播的效果。说实话&#xff0c;一开…

C++、STL标准模板库和泛型编程 ——迭代器、 算法、仿函数(侯捷)

C、STL标准模板库和泛型编程 ——迭代器、 算法、仿函数 &#xff08;侯捷&#xff09; 迭代器iterator_category 算法accumulatefor_eachreplacecountfindsortbinary_search 仿函数 functors(六大部件中最简单的一种&#xff01;) 使用一个东西&#xff0c;却不明白它的道理&a…

Android类似微信首页的页面开发教程(Kotlin)二

前提条件 安装并配置好Android Studio Android Studio Electric Eel | 2022.1.1 Patch 2 Build #AI-221.6008.13.2211.9619390, built on February 17, 2023 Runtime version: 11.0.150-b2043.56-9505619 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. Windows 11 …

【Vue】学习笔记-Vue生命周期

引出生命周期 生命周期 a.又名生命周期回调函数、生命周期函数、生命周期钩子 b.是什么&#xff1a;vue 在关键时刻帮助我们调用一些特殊名称的函数 c.生命周期函数的名字不可更改&#xff0c;但函数的具体内容是程序员根据需求编写的 d.生命周期函数中的this指向是vm或组件实…

拷贝构造与深浅拷贝

文章目录 一、拷贝构造函数二、拷贝初始化三、深浅拷贝 一、拷贝构造函数 如果一个构造函数的第一个参数是自身类型的引用&#xff0c;而且任何额外参数都有默认值&#xff0c;则此构造函数是拷贝构造函数。 class person { public: person(); //默认构造函数 pe…

米文动力 EVO Orin 刷机和克隆操作说明

刷机说明 博主在卸载 cuda 以及 python 后重启后黑屏无法显示&#xff0c;重刷系统才恢复正常。 下载 EVO Orin 用户手册&#xff08;官网没有&#xff0c;所以上传到 CSDN 供下载&#xff09;官网下载 EVO Orin 镜像文件 使用 tar -xvf 解压下载的 bootloader 和镜像包得到 …