Spring Cloud + Vue前后端分离-第11章 用户管理与登录

news2024/11/18 13:46:10

 源代码在GitHub - 629y/course: Spring Cloud + Vue前后端分离-在线课程

Spring Cloud + Vue前后端分离-第11章 用户管理与登录

11-1 增加用户管理功能

用户表设计与基本代码生成

1.用户管理与登录:用户表设计与基本代码生成

all.sql

generatorConfig.xml

ServerGenerator.java

VueGenerator.java

admin.vue

router.js

测试

增加用户名是否已存在校验

1.用户管理与登录:增加用户名是否已存在校验

2.增加自定义业务异常

登录名是不可编辑的,登录名一般会跟其它表有关联,一旦登录名改了,这些关联信息就没有了

UserService.java

根据传入登录名到数据库中查找是否有记录,有记录就说明用户名已经存在

loginName 是唯一的,所以查出来要么没有记录,要么只有一条记录

我们要的功能是校验用户名是否存在,所以也可以把返回值改成true或false,而不是返回User对象,但是这种写法不够通用,所以我们选择返回User。

BusinessException.java

抛出业务异常时,不打印堆栈信息,一方面是提高性能,另一方面是没有业务异常没必要看堆栈信息

继承RuntimeException的一个好处就是代码不需要捕获,如果是直接继承Exception,代码需要捕获,否则编译不通过。

BusinessExceptionCode.java

大家也可以自己把code加上,定义一套异常码

ControllerExceptionHandler.java

user.vue

aria-*的属性主要用于一些读屏设备,方便残障人士使用,比如盲人

测试

这里打印的异常不会打印出堆栈信息,我们之前把堆栈的打印重写了

使用方法:

1.在BusinessExceptionCode.java增加一种异常枚举;

2.在业务代码中抛出指定枚举类型的业务异常

侧边栏激活样式优化

1.用户管理与登录:侧边栏激活样式优化

当前菜单的父菜单的同级菜单,下面所有的子菜单,清空激活样式

admin.vue

可以把active清空掉

测试

随便切换都没有问题

问题:访问跟目录时,页面显示的是admin.vue,但是右边的content部分是空的,没有路由到任何一个子路由

router.js

测试

解决从登录页面跳到控台主页时,侧边栏失效的问题

1.用户管理与登录:解决从登录页面跳到控台主页时,侧边栏失效的问题

问题:从登录页面跳转到控台主页时,菜单失效了

重新刷新,才会显示

打开登录页面时,会去加载所需的js,包括ace.min.js,这里会去做很多的初始化,包括侧边栏的点击事件,但是此时还没有侧边栏

解决方法:进入控台主页时,重新加载ace.min.js

admin.vue

测试成功,登录进来就可以点击出现子模块了

小提示:大家在用很多第三方框架或jquery插件时,如果发现有些功能不起作用,如果看不懂源码,可以尝试这种方法解决,把核心的js重新加载一遍 

11-2 密码的加密传输与加密存储

加密算法MD5与盐值

网站的数据库里已经把原值和密文都算好并存储起来了,如果刚好你输入的密文在数据库里有,就能解(查)出来。

盐值也叫salt值,加上盐值后,密文不容易被破解(查询)。

密码加密传输和加密存储

1.用户管理与登录:密码加密传输和加密存储

存成明文的话,至少程序员可以直接到生产上看到所有人的密码。

从路由器的日志,我可以看到所有人浏览的网站的地址、用户名、密码,如果密码刚好是明文传输,那就泄露了,如果是简单的md5,也很容易被破解,所以需要加个盐值。

md5.js

盐值可以是随机的一串值,但是所有用到盐值的地方必须是同一个值。如果你是做平台系统,有多个客户用你的平台,最好是一个客户一个盐值。

user.vue

这里可以考虑写个通用方法,把hex_md5+盐值包装起来

保险方案:对密码做两层加密

UserController.java

测试

增加修改密码功能

1.用户管理与登录:增加重置密码功能;编辑用户信息的时候不修改密码

修改用户信息和修改密码应该分开,做成两个功能

密码发生改变

user.vue

UserService.java

UserMapper.java

 UserMapper.xml

mybatis-generator 生成的方法里,updateByPrimaryKeySelective会对字段进行非空判断,再更新,如果值为空就不更新,原理就是利用mybatis的if拼成动态sql

测试

user.vue

<div id="edit-password-modal" class="modal fade" tabindex="-1" role="dialog">
      <div class="modal-dialog" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            <h4 class="modal-title">修改密码</h4>
          </div>
          <div class="modal-body">
            <form class="form-horizontal">
              <div class="form-group">
                <label class="control-label col-lg-3">密码</label>
                <div class="col-lg-9">
                  <input class="form-control" type="password" v-model="user.password" name="password">
                </div>
              </div>
            </form>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-white btn-default btn-round" data-dismiss="modal">
              <i class="ace-icon fa fa-times"></i>
              取消
            </button>
            <button type="button" class="btn btn-white btn-info btn-round" v-on:click="savePassword()">
              <i class="ace-icon fa fa-plus blue"></i>
              保存密码
            </button>
          </div>
        </div><!-- /.modal-content -->
      </div><!-- /.modal-dialog -->
    </div><!-- /.modal -->

UserService.java

UserController.java

测试

11-3基本的登录功能开发

基本的登录功能开发

1.用户管理与登录:基本的登录功能开发,校验用户名密码

思考:我现在存到数据库里面的密码是密文,那用户登录的时候,我数据库里面的密码解密不出来了,我怎么知道用户输入的密码对不对?

login.vue

UserController.java

UserService.java

登录验证思考:是否是根据用户名+密码到数据中去查找记录?

用户名+密码去数据库查找的话,程序不知道是用户名不对,还是密码不对。程序应该要能知道,比如我如果发现有大量的用户名不对的报错,说明有人正在不断的探测我系统的用户名。

再次思考:如果用户名不对,提示给前端的是:用户名不存在。如果是密码不对,提示给前端的是:密码不对。是否是这样?

如果你直接告诉前端说用户名不存在,我作为一个黑客的话,可以拿着现成的一堆用户名,包括手机号邮箱,不断的探测哪些用户名是你系统里有了,不要给别人任何机会获取你系统的关键信息。

BusinessExceptionCode.java

LoginUserDto.java

package com.course.server.dto;


public class LoginUserDto {

    /**
    * id
    */
    private String id;

    /**
    * 登录名
    */
    private String loginName;

    /**
    * 昵称
    */
    private String name;
    

    public String getId() {
    return id;
    }

    public void setId(String id) {
    this.id = id;
    }

    public String getLoginName() {
    return loginName;
    }

    public void setLoginName(String loginName) {
    this.loginName = loginName;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", loginName=").append(loginName);
        sb.append(", name=").append(name);
        sb.append("]");
        return sb.toString();
    }
}

UserService.java 

UserController.java

测试

登录后保存登录信息

1.用户管理与登录:登录后前端保存登录信息并显示

前端的信息保存有多种选择:h5的localStorage, sessionStorage; js的全局变量,vue 的store等。

sessionStorage在页面刷新的时候,信息不会丢;关闭页面后,信息自动清空,适合用来存储登录信息。

localStorage在关闭页面后,登录信息还是在的,适合一些内网使用的系统保存登录信息。

用js 全局变量或vue 的store,刷新浏览器的时候,信息会丢失。

login.vue

admin.vue

把登录信息的保存和读取做成通用的方法。

tool.js

小技巧:在获取一些对象的时候,加上|| {},避免获取属性值时报错。

session-storage.js

login.vue

admin.vue

测试

11-4 退出登录与记住登录

增加退出登录功能

1.用户管理与登录:增加退出登录功能,清空前后端的会话缓存

它会动态的改变大小,适应屏幕

退出登录:清空当前登录的缓存信息,并跳到登录页面

admin.vue

一般登录信息在前后端都会保存

Constants.java

UserController.java

测试

增加记住登录信息功能

1.用户管理与登录:增加记住登录信息功能

使用localStorage来保存输入的用户名密码

login.vue

能获取到缓存的值,说明上一次有勾选“记住我” 

如果不清空本地缓存,重新打开页面时,会再次显示记住的用户名密码

测试之前要把浏览器自带的记住用户名密码清除

安全加固,本地缓存保存密码密文

1.用户管理与登录:增加记住登录信息功能,安全加固,本地缓存保存密码密文

local-storage.js

login.vue

测试

从刚才的记住我这个功能,大家可以看出来,一个是记住明文,一个是记住密文,虽然实现的功能是一样的,但是安全性上不一样,所以我们写程序不只是把功能写出来,还要严谨,不要留下坑

11-5 增加登录图形验证码

集成图形验证码kaptcha

1.用户管理与登录:集成图形验证码kaptcha

pom.xml(course)

pom.xml(server)

KaptchaConfig.java

package com.course.server.config;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.util.Config;

import java.util.Properties;

@Configuration
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha getDefaultKaptcha() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        properties.setProperty("kaptcha.border", "no");
//        properties.setProperty("kaptcha.border.color", "105,179,90");
        properties.setProperty("kaptcha.textproducer.font.color", "blue");
        properties.setProperty("kaptcha.image.width", "90");
        properties.setProperty("kaptcha.image.height", "32");
        properties.setProperty("kaptcha.textproducer.font.size", "24");
        properties.setProperty("kaptcha.session.key", "code");
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        properties.setProperty("kaptcha.textproducer.font.names", "Arial");
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
        properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

    //如果项目中有多个页面会用到验证码图片,且图片的大小,颜色等都不一样,就可以增加多个生成验证码图片方法
    @Bean
    public DefaultKaptcha getWebKaptcha() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        properties.setProperty("kaptcha.border", "no");
//        properties.setProperty("kaptcha.border.color", "105,179,90");
        properties.setProperty("kaptcha.textproducer.font.color", "blue");
        properties.setProperty("kaptcha.image.width", "90");
        properties.setProperty("kaptcha.image.height", "45");
        properties.setProperty("kaptcha.textproducer.font.size", "30");
        properties.setProperty("kaptcha.session.key", "code");
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        properties.setProperty("kaptcha.textproducer.font.names", "Arial");
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
        properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

如果项目中有多个页面会用到验证码图片,且图片的大小,颜色等都不一样,就可以增加多个生成验证码图片方法

KaptchaController.java

package com.course.system.controller.admin;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;

@RestController
@RequestMapping("/admin/kaptcha")
public class KaptchaController {

    @Qualifier("getDefaultKaptcha")
    @Autowired
    DefaultKaptcha defaultKaptcha;

    @GetMapping("/image-code/{imageCodeToken}")
    public void imageCode(@PathVariable(value = "imageCodeToken") String imageCodeToken,
    HttpServletRequest request, HttpServletResponse httpServletResponse) throws Exception{
        ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
        try {
            // 生成验证码字符串
            String createText = defaultKaptcha.createText();

            // 将生成的验证码放入会话缓存中,后续验证的时候用到
            request.getSession().setAttribute(imageCodeToken, createText);

            // 使用验证码字符串生成验证码图片
            BufferedImage challenge = defaultKaptcha.createImage(createText);
            ImageIO.write(challenge, "jpg", jpegOutputStream);
        } catch (IllegalArgumentException e) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
        byte[] captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
        httpServletResponse.setHeader("Cache-Control", "no-store");
        httpServletResponse.setHeader("Pragma", "no-cache");
        httpServletResponse.setDateHeader("Expires", 0);
        httpServletResponse.setContentType("image/jpeg");
        ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
        responseOutputStream.write(captchaChallengeAsJpeg);
        responseOutputStream.flush();
        responseOutputStream.close();
    }
}

测试http://localhost:9000/system/admin/kaptcha/image-code/123

alt+enter,将字符加到字典里,就不会有波浪线警告了 

页面显示验证码及刷新验证码

1.用户管理与登录:页面显示验证码及刷新验证码

login.vue

tool.js

radix 默认63
10个数字+26个大小字母+26个小写字母,共62个字符,可以用来表示62进制的数值,也可以加入一些特殊字符,组成更大进制的数。
原理:以62进制为例,随机生成一个0~61的数值,比如41,那边就取chars数组中的第41个字符,这样重复做8遍,就生成了8位的62进制数,重复的概览是62的8次方。也可以生成更长的数值。

登录增加验证码校验

1.用户管理与登录:登录增加验证码校验

2.解决每次ajax请求,对应的sessionId不一致的问题

UserDto.java

增加属性后,记得alt+insert生成get, set, toString()方法。比如lombok,代码侵入性太强,如果我用了插件,那大家都得安装这个插件,否则会报错。

UserController.java

在登录里面,增加验证码校验:通过token去缓存中获取验证码字符串,并和用户输入的字符串做比较。

main.js

登录验证出错时,将密码清空,同时刷新验证码图片 

login.vue

 

 

测试

刷新验证码会让网站更安全,但是会牺牲一点用户体验,需要折中选择

测试一种场景:刷新过的验证码,还能不能用。

正规的项目中,都会有专门的测试团队来编写测试用例。如果是个人开发,只能自己设计测试用例,要尽可能的覆盖各种使用场景

11-6 单点登录功能开发

单点登录解决方案介绍

生产发布时,至少是双节点,防止单台宕机。

不管是在哪一台做的登录,登录完成后,会把登录信息保存到redis中。当业务请求进来时,再到redis中获取登录信息,能获取到就表示已登录,未获取到就表示未登录,拦截掉请求。

功能:只要在其中一个产品中登录过,其他关联的产品都不需要再登录。

token:登录标识,每个用户每次登录,都会生成不同的token。

单点登录(Single Sign On),简称为SSO,核心功能:session共享。

需要解决Session共享的场景:

1.同个应用多节点共享登录信息;

2.多个项目间共享登录信息。

一般我们通常说的单点登录系统,是用来解决场景2的。

集成redis

1.用户管理与登录:集成redis,图片验证码的存储从session改为redis

pom.xml(server)

application.properties

KaptchaController.java

UserController.java

回归验证码功能,更换缓存不要对原有流程产生影响

测试

生成登录token并存储到redis中

1.用户管理与登录:生成登录token并存储到redis中,退出登录时删除token

LoginUserDto.java

UserController.java

这里也可以直接保存loginUserDto对象,但是需要序列化。如果是跨应用使用的,比如A应用存,B应用取,一般会把值转成JSON字符串

admin.vue

KaptchaController.java

测试

11-7 前后端登录拦截

基于Vue路由的登录拦截

1.用户管理与登录:基于Vue路由的登录拦截

此时,直接访问user页面,是可以直接跳过身份验证,这是绝对不允许的

router.js

只需要在父路由增加拦截就可以了,子路由就会都有这个拦截,不需要子路由再一个一个添加 

main.js

测试

但是现在还有一个问题:界面拦住了,但是所有的接口都可以直接访问,非常危险,这个就是基于后端的请求拦截

在请求headers 中统一增加token

1.用户管理与登录:在请求headers 中统一增加token

main.js

可以用这种方法给所有请求加了统一的系统参数,比如在header里加上请求流水,请求时间等。

测试

gateway增加登录拦截

1.用户管理与登录:在gateway中增加登录拦截

application.properties

LoginAdminGatewayFilter.java

LoginAdminGatewayFilterFactory.java

测试

不是所有的请求都需要做登录拦截,比如登录接口、验证码图片接口

gateway实现控台登录拦截功能

1.用户管理与登录:gateway实现控台登录拦截功能

pom.xml(course)

pom.xml(gateway)

gateway模块并没有依赖server模块,所以有些jar包如redis , json等,需要单独在pom.xml中增加依赖

application.properties

LoginAdminGatewayFilter.java

package com.course.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;

@Component
public class LoginAdminGatewayFilter implements GatewayFilter, Ordered {
    private static final Logger LOG = LoggerFactory.getLogger(LoginAdminGatewayFilter.class);

    @Resource
    private RedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();

        //请求地址中不包含/admin/的,不是控台请求,不需要拦截
        if (!path.contains("/admin/")){
            return chain.filter(exchange);
        }
        if (path.contains("/system/admin/user/login")
         || path.contains("/system/admin/user/logout")
         || path.contains("/system/admin/kaptcha")){
            LOG.info("不需要控台登录验证:{}",path);
            return chain.filter(exchange);
        }

        //获取header的token参数
        String token = exchange.getRequest().getHeaders().getFirst("token");
        LOG.info("控台登录验证开始,token:{}",token);
        if (token == null || token.isEmpty()){
            LOG.info("token为空,请求被拦截");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        Object object = redisTemplate.opsForValue().get(token);
        if (object == null){
            LOG.warn("token无效,请求被拦截");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }else {
            LOG.info("已登录:{}",object);
            return chain.filter(exchange);
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

http协议中有一些约定好的状态码,比如401未授权,404未找到,200正常处理等。

HttpStatus.java

测试

 直接访问这个地址,是会报错的

11-8 用户登录流程图

白底是前端部分,蓝底是后端部分

验证码流程

开始->生成验证码token唯一标识->调用服务端图形验证码接口->生成验证码字符串->以token为key将字符串放入redis中,设置时效->返回验证码图片->结束

登录流程

开始->输入用户名、密码、验证码->前端密码加密(防止数据传输时泄漏)->组装登录参数包含验证码token->调用登录接口->密码加密->根据token到redis获取正确的验证码->

验证码正确?(这里的验证码错误分为两种,验证码已过期和验证码不正确)

1.->清除redis验证码根据用户名查询->用户存在?

        -是>比较密码正确?

                -是>生成登录token->以token为key将登录信息放入缓存中->返回用户信息-----success=true----->

                -否>打印日志:密码错->返回用户名不存在或密码错误----- success=false----->success=true----->

        -否>打印日志:用户名不存在->返回用户名不存在或密码错误

2.->打印日志并返回验证码错误----- success=false----->success=true----->

success=true?

-是>保存用户信息到h5 session缓存->勾选记住我

        -是>保存用户名密码到h5 local缓存中->结束

        -否>清空h5 local缓存中的用户密码->结束

-否>弹出登录失败提示框->结束

                                                                                                                                     |

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

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

相关文章

Unity组件开发--AB包打包工具

1.项目工程路径下创建文件夹&#xff1a;ABundles 2.AB包打包脚本&#xff1a; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement;public class AssetBundle…

Python从入门到网络爬虫(OS模块详解)

前言 本章介绍python自带模块os&#xff0c;os为操作系统 operating system 的简写&#xff0c;意为python与电脑的交互。os 模块提供了非常丰富的方法用来处理文件和目录。通过使用 os 模块&#xff0c;一方面可以方便地与操作系统进行交互&#xff0c;另一方面页可以极大增强…

C语言如何提高程序的可读性?

一、问题 可读性是评价程序质量的一个重要标准&#xff0c;直接影响到程序的修改和后期维护&#xff0c;那么如何提高程序的可读性呢? 二、解答 提高程序可读性可以从以下几方面来进行。 &#xff08;1&#xff09;C程序整体由函数构成的。 程序中&#xff0c;main()就是其中…

Maven依赖冲突解决

Maven介绍 Maven 是 Apache 软件基金会唯一维护的一款自动化构建工具&#xff0c;专注于服务Java平台的项目构建和依赖管理。 1.Maven是如何寻找依赖的? 首先会去本地仓库寻找&#xff0c;然后会去公司的私服仓库寻找&#xff0c;一般私服仓库存的都是公司自己开发的 jar 包&…

蚁群算法解决旅行商问题的完整Python实现

蚁群算法&#xff08;Ant Colony Optimization&#xff0c;简称ACO&#xff09;是一种模拟蚂蚁觅食行为的启发式优化算法。它通过模拟蚂蚁在寻找食物时释放信息素的行为&#xff0c;来解决组合优化问题&#xff0c;特别是旅行商问题&#xff08;TSP&#xff09;。 蚁群算法的基…

RAG:让大语言模型拥有特定的专属知识

作为一个在Chatbot领域摸爬滚打了7年的从业者&#xff0c;笔者可以诚实地说&#xff0c;在大语言模型的推动下&#xff0c;检索增强生成&#xff08;Retrieval Augmented Generation&#xff0c;RAG&#xff09;技术正在快速崛起。 RAG的搜索请求和生成式AI技术&#xff0c;为搜…

【数据库原理】期末突击(2)

有不会的题可以后台问我的哦&#xff0c;看见了就会回。祝大家期末心想事成。 数据库大题显而易见就只有几类&#xff0c;大家可以根据老师平时讲解的例题对应去解决一类型的题目。 前提知识; 候选码候选键关键字键码 关系代数&#xff1a;&#xff08;1&#xff09;传统&…

echarts使用之柱状图

一、引入Echarts npm install eacharts --save 二、选择一个Echarts图 选择创建一个柱状图 option { // x轴参数的基本配置xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun] //X轴数据}, // y轴参数的基本配置yAxis: {type: value}, // series:[{data: …

MySQL连接池、C3P0、Druid德鲁伊连接池技术

MySQL连接池 连接池C3P0Druid 连接池 概念&#xff1a;其实就是一个容器(集合)&#xff0c;存放数据库连接的容器。当系统初始化好后&#xff0c;容器被创建&#xff0c;容器中会申请一些连接对象&#xff0c;当用户来访问数据库时&#xff0c;从容器中获取连接对象&#xff0…

怎么样检查自己系统上的Python环境中是否有某个包(扩展库)?

比如我们这里想看下有没有库pytz 很简单&#xff0c;进入Python的命令行&#xff0c;然后输入下面的命令&#xff1a; import pytz如果有这个库&#xff0c;则不会报错&#xff0c;否则会报错。 Windows的测试结果如下&#xff1a; Centos的测试结果如下&#xff1a;

Linux中vim查看文件某内容

一、编辑文件命令 [rootyinheqilin ~]# vim test.txt 1&#xff0c;在编辑的文件中连续按2次键盘的【g】键&#xff0c;光标会移动到文档开头第一行 2&#xff0c;输入一个大写 G&#xff0c;光标会跳转到文件的最后一行第一列&#xff08;末行) 二、查看文件内容命令 gre…

改善制造业客户体验的实用技巧与策略

制造业是一个关键行业&#xff0c;在经济中发挥着至关重要的作用。它负责为我们的日常生活生产必需品和服务。然而近年来&#xff0c;该行业发生了重大变化&#xff0c;企业现在面临着日益激烈的竞争和满足消费者需求的压力。为了保持竞争力&#xff0c;制造商必须专注于打造更…

算法通关村番外篇-LeetCode编程从0到1系列二

大家好我是苏麟 , 今天来说LeetCode编程从0到1系列二 . 内置函数 最后一个单词的长度 描述 : 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子…

PHP大型企业人才招聘网站源码带文字安装教程

PHP大型企业人才招聘网站源码带文字安装教程 运行环境 服务器宝塔面板 PHP 5.6 Mysql 5.5及以上版本 Linux Centos7以上 功能说明&#xff1a; 会员模块包括企业管理、职位管理、名企管理、企业认证审核、 企业设置。内容模块包括新闻管理、新闻类别、新闻属性、添加新闻。 运营…

海外云手机:一机多用,引领跨境电商新潮流

如今&#xff0c;跨境卖家除了经营跨境平台外&#xff0c;还需抓住短视频和社交媒体的机遇。在社交媒体上实现引流&#xff0c;将流量导向自己的跨境平台或独立站&#xff0c;吸引用户购买&#xff0c;完成流量变现&#xff0c;已成为跨境电商的一项关键策略。本文将介绍如何用…

获取深层次字段报错TypeError: Cannot read properties of undefined (reading ‘title‘)

动态生成菜单时报错,不能多层获取路由meta下面的title字段 <template><p>{{ meneList }}</p><template v-for"item in meneList" :key"item.path"><el-menu-item v-if"!item.children"><template #title>{…

【Tomcat】在一台计算机上运行两个Tomcat服务

首先把Tomcat整个文件复制一份放在其他文件夹路径中 1.修改环境变量 添加环境变量在系统变量里面 “CATALINA_HOME” 指向一个Tomcat文件夹路径 “CATALINA_HOME1” 指另一个Tomcat文件夹路径 2.修改startup里面的环境变量&#xff0c;全部修改 分别修改两个apache-tomcat…

教你用SadTalker一键整合包轻松制作专属数字人

数字人的效果&#xff1a; &#x1f3b5;我用SadTalker制作了专属虚拟数字人&#xff0c;还会唱歌哦&#xff0c;多多点赞关注就出教程呦&#x1f497; SadTalker有独立离线版Ai数字人&#xff0c;也可以在Stable Diffusion以插件的形式使用&#xff0c;但是如果显卡小的话还是…

水经微图安卓版APP正式上线!

在水经微图APP&#xff08;简称“微图APP”&#xff09;安卓版已正式上线&#xff01; 在随着IOS版上线约一周之后&#xff0c;安卓版终于紧随其后发布了。 微图安卓版APP下载安装 自从IOS版发布之后&#xff0c;就有用户一直在问安卓版什么时候发布&#xff0c;这里非常感谢…

PHP Web应用程序中常见漏洞

一淘模板&#xff08;56admin.com)发现PHP 是一种流行的服务器端脚本语言&#xff0c;用于开发动态 Web 应用程序。但是&#xff0c;与任何其他软件一样&#xff0c;PHP Web 应用程序也可能遭受安全攻击。 在本文中&#xff0c;我们将讨论 PHP Web 应用程序中一些最常见的漏洞…