【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【17】认证服务01

news2024/12/28 3:30:33

持续学习&持续更新中…

守破离


【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【17】认证服务01

  • 环境搭建
  • 验证码倒计时
  • 短信服务
  • 邮件服务
  • 验证码
    • 短信形式:
    • 邮件形式:
  • 异常机制
  • MD5
  • 参考

环境搭建

C:\Windows\System32\drivers\etc\hosts

192.168.56.10 gulimall.com
192.168.56.10 search.gulimall.com
192.168.56.10 item.gulimall.com
192.168.56.10 auth.gulimall.com

Nginx配置:(记得使用Nginx动静分离)

在这里插入图片描述

# ...

http {
    # ...

    upstream gulimall {
       server 192.168.193.107:88;
    }

    include /etc/nginx/conf.d/*.conf;
}

网关:

        - id: gulimall_auth_route
          uri: lb://gulimall-auth
          predicates:
            - Host=auth.gulimall.com

gulimall-auth:

@Controller
public class LoginController {
    @GetMapping("/login.html")
    public String loginPage() {
        return "login";
    }

    @GetMapping("/reg.html")
    public String regPage() {
        return "reg";
    }
}

或者:

@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
    /**
     * 视图映射
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {

        /**
         *     @GetMapping("/login.html")
         *     public String loginPage(){
         *          //空方法
         *         return "login";
         *     }
         */
        //只是get请求能映射
        registry.addViewController("/login.html").setViewName("login");
        registry.addViewController("/reg.html").setViewName("reg");
    }
}

验证码倒计时

前端:

在这里插入图片描述

    $(function () {
        $("#sendCode").click(function () {
            //2、倒计时
            if ($(this).hasClass("disabled")) {
                //正在倒计时。
            } else {
                //1、给指定手机号发送验证码
                // $.get("/sms/sendEmail?email=" + $("#phoneNum").val(), function (data) {
                $.get("/sms/sendcode?phone=" + $("#phoneNum").val(), function (data) {
                    if (data.code != 0) {
                        alert(data.msg);
                    }
                });
                timeoutChangeStyle();
            }
        });
    })

    var num = 60;

    function timeoutChangeStyle() {
        $("#sendCode").attr("class", "disabled");
        if (num == 0) {
            $("#sendCode").text("发送验证码");
            num = 60;
            $("#sendCode").attr("class", "");
        } else {
            var str = num + "s 后再次发送";
            $("#sendCode").text(str);
            setTimeout("timeoutChangeStyle()", 1000);
        }
        num--;
    }

短信服务

购买短信套餐后,扫码激活,然后绑定测试手机号码:

在这里插入图片描述

然后点击:调用API发送短信 按钮 (使用【专用】测试签名/模板)

在这里插入图片描述

然后 发起调用 ,复制相关信息即可

在这里插入图片描述

增加权限授予RAM子账号SMS和MPush的权限。

在这里插入图片描述

        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>alibabacloud-dysmsapi20170525</artifactId>
            <version>3.0.0</version>
        </dependency>
// This file is auto-generated, don't edit it. Thanks.
package com.atguigu.gulimall.auth.sms;

import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.sdk.service.dysmsapi20170525.AsyncClient;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsResponse;
import com.google.gson.Gson;
import darabonba.core.client.ClientOverrideConfiguration;

import java.util.concurrent.CompletableFuture;

public class SendSms {
    public static void main(String[] args) throws Exception {

        // HttpClient Configuration
        /*HttpClient httpClient = new ApacheAsyncHttpClientBuilder()
                .connectionTimeout(Duration.ofSeconds(10)) // Set the connection timeout time, the default is 10 seconds
                .responseTimeout(Duration.ofSeconds(10)) // Set the response timeout time, the default is 20 seconds
                .maxConnections(128) // Set the connection pool size
                .maxIdleTimeOut(Duration.ofSeconds(50)) // Set the connection pool timeout, the default is 30 seconds
                // Configure the proxy
                .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("<your-proxy-hostname>", 9001))
                        .setCredentials("<your-proxy-username>", "<your-proxy-password>"))
                // If it is an https connection, you need to configure the certificate, or ignore the certificate(.ignoreSSL(true))
                .x509TrustManagers(new X509TrustManager[]{})
                .keyManagers(new KeyManager[]{})
                .ignoreSSL(false)
                .build();*/

        // Configure Credentials authentication information, including ak, secret, token
        StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
                // Please ensure that the environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET are set.
                .accessKeyId("xxxx")
                .accessKeySecret("xxxx")
                //.securityToken(System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")) // use STS token
                .build());

        // Configure the Client
        AsyncClient client = AsyncClient.builder()
                .region("cn-shanghai") // Region ID
                //.httpClient(httpClient) // Use the configured HttpClient, otherwise use the default HttpClient (Apache HttpClient)
                .credentialsProvider(provider)
                //.serviceConfiguration(Configuration.create()) // Service-level configuration
                // Client-level configuration rewrite, can set Endpoint, Http request parameters, etc.
                .overrideConfiguration(
                        ClientOverrideConfiguration.create()
                                  // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
                                .setEndpointOverride("dysmsapi.aliyuncs.com")
                        //.setConnectTimeout(Duration.ofSeconds(30))
                )
                .build();

        // Parameter settings for API request
        SendSmsRequest sendSmsRequest = SendSmsRequest.builder()
                .signName("阿里云短信测试")
                .templateCode("xxxx")
                .phoneNumbers("xxxx")
                .templateParam("{\"code\":\"1111\"}")
                // Request-level configuration rewrite, can set Http request parameters, etc.
                // .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders()))
                .build();

        // Asynchronously get the return value of the API request
        CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);
        // Synchronously get the return value of the API request
        SendSmsResponse resp = response.get();
        System.out.println(new Gson().toJson(resp));
        // Asynchronous processing of return values
        /*response.thenAccept(resp -> {
            System.out.println(new Gson().toJson(resp));
        }).exceptionally(throwable -> { // Handling exceptions
            System.out.println(throwable.getMessage());
            return null;
        });*/

        // Finally, close the client
        client.close();
    }

}

简单把这些代码整改一下:

@Configuration
public class SMSConfig {

    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;
    @Value("${spring.cloud.alicloud.secret-key}")
    private String secretKey;

    @Bean
    public StaticCredentialProvider provider() {
       return StaticCredentialProvider.create(Credential.builder().accessKeyId(accessId).accessKeySecret(secretKey).build());
    }

}
@RestController
public class SendSmsController {

    @Autowired
    private StaticCredentialProvider provider;

    /**
     * 提供接口,供别的服务调用
     *
     * @param phone
     * @param code
     * @return "body": {
     * "bizId": "774515119736291045^0",
     * "code": "OK",
     * "message": "OK",
     * "requestId": "D6BD5A90-8755-5C82-B631-0F40AB7B41B0"
     * }
     */
    @GetMapping("/sms/send")
    public R sendSms(@RequestParam("phone") String phone, @RequestParam("code") String code) throws ExecutionException, InterruptedException {

        AsyncClient client = AsyncClient.builder().region("cn-shanghai") // Region ID
                .credentialsProvider(provider).overrideConfiguration(ClientOverrideConfiguration.create().setEndpointOverride("dysmsapi.aliyuncs.com")).build();

        SendSmsRequest sendSmsRequest = SendSmsRequest.builder().signName("阿里云短信测试").templateCode("SMS_154950909").phoneNumbers(phone).templateParam("{\"code\":\"" + code + "\"}").build();

        CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);
        SendSmsResponse resp = response.get();

        /*
            {
                "headers": {
                    "Keep-Alive": "timeout\u003d25" ......
                },
                "statusCode": 200,
                "body": {
                    "bizId": "774515119736291045^0",
                    "code": "OK",
                    "message": "OK",
                    "requestId": "D6BD5A90-8755-5C82-B631-0F40AB7B41B0"
                }
            }
         */
        client.close();

        if (resp.getBody().getMessage().equalsIgnoreCase("OK")) return R.ok();
        return R.error(BizCodeEnume.SMS_SEND_EXCEPTION);
    }

}

邮件服务

<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4.1</version>
</dependency>
@Data
public class EmailVo {
    private String receiveMail;
    private String subject;
    private String content;
}

@Configuration
public class EmailConfig {

// 我在Nacos配置中心配的user和password
    @Value("${mail.user}")
    private String mailUser;

    @Value("${mail.password}")
    private String mailPassword;

    @Bean
    public Properties props() {
        // 创建Properties 类用于记录邮箱的一些属性
        Properties props = new Properties();
        // 表示SMTP发送邮件,必须进行身份验证
        props.put("mail.smtp.auth", "true");
        //此处填写SMTP服务器
        props.put("mail.smtp.host", "smtp.qq.com");
        //端口号,QQ邮箱端口587
        props.put("mail.smtp.port", "587");
        // 此处填写,写信人的账号
        props.put("mail.user", mailUser);
        // 此处填写16位STMP口令
        props.put("mail.password", mailPassword);
        return props;
    }

    @Bean
    public Authenticator authenticator(Properties props) {
        // 构建授权信息,用于进行SMTP进行身份验证
        return new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                // 用户名、密码
                String userName = props.getProperty("mail.user");
                String password = props.getProperty("mail.password");
                return new PasswordAuthentication(userName, password);
            }
        };
    }
}
@RestController
public class SendEmailController {

    @Autowired
    private Properties props;

    @Autowired
    private Authenticator authenticator;

    @PostMapping("/email/send")
    public R sendEmail(@RequestBody EmailTo emailTo) throws MessagingException {
        // 使用环境属性和授权信息,创建邮件会话
        Session mailSession = Session.getInstance(props, authenticator);
        // 创建邮件消息
        MimeMessage message = new MimeMessage(mailSession);
        // 设置发件人
        InternetAddress form = new InternetAddress(props.getProperty("mail.user"));
        message.setFrom(form);
        // 设置收件人的邮箱
        InternetAddress to = new InternetAddress(emailTo.getReceiveMail());
        message.setRecipient(Message.RecipientType.TO, to);
        // 设置邮件标题
        message.setSubject(emailTo.getSubject());
        // 设置邮件的内容体
        message.setContent(emailTo.getContent(), "text/html;charset=UTF-8");
        // 最后当然就是发送邮件啦
        Transport.send(message);

        return R.ok();
    }

}

在这里插入图片描述

验证码

短信形式:

    @GetMapping("/sms/sendcode")
    public R sendCode(@RequestParam("phone") String phone) {
//        Redis缓存验证码:存起来方便下次校验 以及 可以给验证码设置有效期

        String code = getRandomCode().toString();

//        防止同一个手机号在60s内再次发送验证码
        String key = AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone;

        String oldCode = stringRedisTemplate.opsForValue().get(key);
        if (!StringUtils.isEmpty(oldCode)) {
            long l = Long.parseLong(oldCode.split("_")[1]);
            if (System.currentTimeMillis() - l < 60000) { // 如果时间间隔小于60s
                return R.error(BizCodeEnume.SMS_MULTI_EXCEPTION);
            }
        }

//        R r = thirdPartyFeignService.sendSms(phone, code);
//        if (r.getCode() == BizCodeEnume.SUCCESS.getCode()) {
//            code = code + "_" + System.currentTimeMillis();
//            stringRedisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES); //过期时间5分钟
//        }
//        return r;

        CompletableFuture.runAsync(() -> thirdPartyFeignService.sendSms(phone, code), threadPool);
        CompletableFuture.runAsync(() -> {
            stringRedisTemplate.opsForValue().set(key, codeResolve(code), 5, TimeUnit.MINUTES); //过期时间5分钟
        }, threadPool);
        return R.ok();
    }

生成验证码(随机四位数):

    private Integer getRandomCode() {
        //4位数字验证码:想要[1000,9999],也就是[1000,10000)

        // Math.random() -> [0, 1)  // (int) Math.random()永远为0
        // Math.random() * (end - begin) -> [0, end - begin)
        // begin + Math.random() * (end - begin) -> [begin, end)
        int code = (int) (1000 + Math.random() * (10000 - 1000));
        return code;
    }

邮件形式:

    @GetMapping("/sms/sendEmail")
    public R sendEmailCode(@RequestParam("email") String email) throws MessagingException {
        String code = UUID.randomUUID().toString().substring(0, 5);
        String key = AuthServerConstant.EMAIL_CODE_CACHE_PREFIX + email;

        String oldCode = stringRedisTemplate.opsForValue().get(key);
        if (!StringUtils.isEmpty(oldCode)) { // 说明5分钟内已经给该邮箱发送过验证码了
            long l = Long.parseLong(oldCode.split("_")[1]);
            if (System.currentTimeMillis() - l < 60000) { // 如果时间间隔小于60s
                return R.error(BizCodeEnume.SMS_MULTI_EXCEPTION);
            }
        }

        CompletableFuture.runAsync(() -> {
            // 给Redis放置验证码
            String realSaveCode = code + "_" + System.currentTimeMillis();
            stringRedisTemplate.opsForValue().set(key, realSaveCode, 5, TimeUnit.MINUTES); //过期时间5分钟
        }, threadPool);

        CompletableFuture.runAsync(() -> {
            // 发送邮件
            try {
                EmailTo emailTo = new EmailTo();
                emailTo.setReceiveMail(email);
                emailTo.setContent("验证码:" + code + "——有效期5分钟!");
                emailTo.setSubject("欢迎注册!");
                thirdPartyFeignService.sendEmail(emailTo);
            } catch (MessagingException e) {
                e.printStackTrace();
            }
        }, threadPool);

        return R.ok();
    }

异常机制

    @PostMapping("/regist")
    public R regist(@RequestBody MemberRegistVo vo){
        try{
            memberService.regist(vo);
        }catch (PhoneExistException e){
            return R.error(BizCodeEnume.PHONE_EXIST_EXCEPTION);
        }catch (UsernameExistException e){
            return R.error(BizCodeEnume.USER_EXIST_EXCEPTION);
        }
        return R.ok();
    }
    @Override
    public void regist(MemberRegistVo vo) {
        //检查用户名和手机号是否唯一。为了让controller能感知异常:异常机制
        String phone = vo.getPhone(); checkPhoneUnique(phone);
        String userName = vo.getUserName(); checkUsernameUnique(userName);

        MemberEntity entity = new MemberEntity();
        entity.setMobile(phone);
        entity.setUsername(userName);
        entity.setNickname(userName);

        //设置默认等级
        MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
        entity.setLevelId(levelEntity.getId());

        //密码要进行加密存储。//当然,也可以在前端就加密发过来
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encode = passwordEncoder.encode(vo.getPassword());
        entity.setPassword(encode);

        //其他的默认信息
        //保存
        this.baseMapper.insert(entity);
    }
    @Override
    public void checkPhoneUnique(String phone) throws PhoneExistException {
        Integer mobile = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
        if (mobile > 0) {
            throw new PhoneExistException();
        }
    }

    @Override
    public void checkUsernameUnique(String username) throws UsernameExistException {
        Integer count = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", username));
        if (count > 0) {
            throw new UsernameExistException();
        }
    }
public class UsernameExistException extends RuntimeException {
    public UsernameExistException() {
        super("用户名存在");
    }
}

R:

public class R extends HashMap<String, Object> {
    public static final String CODE = "code";
    public static final String MSG = "msg";
    public static final String DATA = "data";

    //利用fastjson进行逆转
    public <T> T getData(String key, TypeReference<T> typeReference) {
        Object data = get(key);// 默认是map
        String s = JSON.toJSONString(data); // 得转为JSON字符串
        T t = JSON.parseObject(s, typeReference);
        return t;
    }

    //利用fastjson进行逆转
    public <T> T getData(TypeReference<T> typeReference) {
        return getData(DATA, typeReference);
    }

    public R setData(Object data) {
        put(DATA, data);
        return this;
    }

    public R() {
        put(CODE, BizCodeEnume.SUCCESS.getCode());
        put(MSG, BizCodeEnume.SUCCESS.getMsg());
    }

    public static R error() {
        return error("服务器未知异常,请联系管理员");
    }

    public static R error(String msg) {
//        500
        return error(org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put(CODE, code);
        r.put(MSG, msg);
        return r;
    }

    public static R error(BizCodeEnume bizCodeEnume) {
        R r = new R();
        r.put(CODE, bizCodeEnume.getCode());
        r.put(MSG, bizCodeEnume.getMsg());
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put(MSG, msg);
        return r;
    }

    public static R ok(Map<String, Object> map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok() {
        return new R();
    }

    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    public Integer getCode() {
        return (Integer) this.get(CODE);
    }

    public String getMsg() {
        return (String) this.get(MSG);
    }
}
/***
 * TODO 写博客
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为5位数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。
 *      10:通用             000:系统未知异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 *  10: 通用
 *      001:参数格式校验
 *  11: 商品
 *  12: 订单
 *  13: 购物车
 *  14: 物流
 */
public enum BizCodeEnume {
    SUCCESS(0, "OK"),
    HTTP_SUCCESS(200, "OK"),

    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败"),
    TOO_MANY_REQUEST(10002,"请求流量过大"),
    SMS_MULTI_EXCEPTION(10003,"验证码获取频率太高,请1分钟后再试"),
    SMS_SEND_EXCEPTION(10004,"验证码发送失败"),
    SMS_CODE_EXCEPTION(10005,"验证码错误"),
    REG_ERROR_EXCEPTION(10006,"用户名或手机已存在,注册失败"),

    PRODUCT_UP_EXCEPTION(11000,"商品上架异常"),
    USER_EXIST_EXCEPTION(15001,"用户存在"),
    PHONE_EXIST_EXCEPTION(15002,"手机号存在"),
    NO_STOCK_EXCEPTION(21000,"商品库存不足"),
    LOGINACCT_PASSWORD_INVAILD_EXCEPTION(15003,"账号密码错误");

    private final int code;
    private final String msg;

    BizCodeEnume(int code,String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

MD5

MD5:Message Digest algorithm 5,信息摘要算法

  • 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
  • 容易计算:从原数据计算出MD5值很容易。
  • 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
  • 强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
  • 不可逆(即使知道加密算法,也不能反推出明文密码): MD5是一种信息摘要算法,会损 失元数据,所以不可逆出原数据是什么

但是,由于MD5的抗修改性和强抗碰撞(一个字符串的MD5值永远是那个值),发明了彩虹表(暴力 破解)。所以,MD5不能直接进行密码的加密存储

加盐:

  • 通过生成随机数与MD5生成字符串进行组合
  • 数据库同时存储MD5值与salt值。验证正确性时使用salt进行MD5即可
百度网盘的秒传:在上传文件之前,计算出该文件的MD5值,看有没有人之前上传过,也就是去匹配百度网盘的数据库中有没有相同的 MD5 值, 如果有一样的就不用传了 
@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallAuthApplicationTests {
    @Test
    public void contextLoads() {
        //MD5是不可逆的,但是利用它的抗修改性(一个字符串的MD5值永远是那个值),发明了彩虹表(暴力破解)。
        //所以,MD5不能直接进行密码的加密存储;
//        String s = DigestUtils.md5Hex("123456");

        //盐值加密;随机值 加盐 :$1$ + 8位字符
//        只要是同一个材料,做出来的饭是一样的,如果给饭里随机撒点“盐”,那么,饭的口味就不一样了
        //"123456"+System.currentTimeMillis();

        //想要再次验证密码咋办?: 将密码再进行盐值(去数据库查当时保存的随机盐)加密一次,然后再去匹配密码是否正确
//        String s1 = Md5Crypt.md5Crypt("123456".getBytes()); //随机盐
//        String s1 = Md5Crypt.md5Crypt("123456".getBytes(),"$1$qqqqqqqq"); //指定盐
//        System.out.println(s1);

//        给数据库加字段有点麻烦,Spring有好用的工具:
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//        String encode = passwordEncoder.encode("123456");
//        $2a$10$coLmFyeppkTPTfD0RJgqL.nx33s0wvUmj.shqEM/6hvwOO4TWiGmy
//        $2a$10$4IP4F/2iFO2gbSvQKyJzGuI3RhU5Qdtr519KsyoXGAy.b7WT4P1RW
//        $2a$10$0hEI3vMkTbTqK76990MGu.s9QKrkjDSpgyhfzR4zsy07oKB9Jw.PS

//        System.out.println(encode);
//        boolean matches = passwordEncoder.matches("123456", "$2a$10$0hEI3vMkTbTqK76990MGu.s9QKrkjDSpgyhfzR4zsy07oKB9Jw.PS");
        boolean matches = passwordEncoder.matches("lpruoyu123", "$2a$10$m7TmOQAin5Tj6QzV1TT0ceW6iLypdN8LHkYP16DUEngJUfYNgWVEm");
        System.out.println(matches);
    }
}

在这里插入图片描述

参考

雷丰阳: Java项目《谷粒商城》Java架构师 | 微服务 | 大型电商项目.


本文完,感谢您的关注支持!


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

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

相关文章

2024年软件测试面试题,精选100道,内附文档。。。

测试技术面试题 1、我现在有个程序&#xff0c;发现在 Windows 上运行得很慢&#xff0c;怎么判别是程序存在问题还是软硬件系统存在问题&#xff1f; 2、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 3、测试的策略有哪些&#xff1f; 4、正交表测试用…

lua中判断2个表是否相等

当我们获取 table 长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数&#xff0c;而导致无法正确取得 table 的长度&#xff0c;而且还会出现奇怪的现象。例如&#xff1a;t里面有3个元素&#xff0c;但是因为最后一个下表是5和4&#xff0c;却表现出不一…

mac M2芯片系统版本macOS Sonoma14.4.1 Navicat Premium意外退出问题,报错:Translated Report (Full Report Below)

前言 Mac电脑正在使用的navicat客户端突然闪退了&#xff01;&#xff01;&#xff01;&#xff01; 之前用的好好的。做了可能影响navicat客户端闪退的事情就是把电脑系统升级到了macOS Sonoma14.4.1。后悔莫及&#xff5e; 现象&#xff1a;navicat能正常创建连接&#xff0…

大数据处理引擎选型之 Hadoop vs Spark vs Flink

随着大数据时代的到来&#xff0c;处理海量数据成为了各个领域的关键挑战之一。为了应对这一挑战&#xff0c;多个大数据处理框架被开发出来&#xff0c;其中最知名的包括Hadoop、Spark和Flink。本文将对这三个大数据处理框架进行比较&#xff0c;以及在不同场景下的选择考虑。…

【AI是在帮助开发者还是取代他们?】AI与开发者:合作与创新的未来

目录 前言一、AI工具现状&#xff08;一&#xff09;GitHub Copilot&#xff08;二&#xff09;TabNine 二、AI对开发者的影响&#xff08;一&#xff09;影响和优势&#xff08;二&#xff09;新技能和适应策略&#xff08;三&#xff09;保持竞争力的策略 三、AI开发的未来&a…

CAS操作

CAS 全称:Compare and swap,能够比较和交换某个寄存器中的值和内存中的值,看是否相等,如果相等,则把另外一个寄存器中的值和内存进行交换. (这是一个伪代码,所以这里的&address实际上是想要表示取出address中的值) 那么我们可以看到,CAS就是这样一个简单的交换操作,那么…

为什么网上商店需要翻译成其他语言

网上商店不仅仅是一个可以买到商品的网站。它是一个完整的电子商务平台&#xff0c;为来自世界各地的用户提供购买所需物品的机会。但是&#xff0c;为了让这些用户舒适地使用网站&#xff0c;需要高质量的翻译和本地化。 本地化是指产品或服务适应特定文化或市场的过程。它包…

app单页下载页源码带管理后台

新版带后台管理APP应用下载页,自动识别安卓苹果下载页&#xff0c;带管理后台&#xff0c;内置带3套App下载模板带中文模板/英文模板随时切换。 app单页下载页源码带管理后台

从头开始构建 RAG 的 LLM 代理:综合指南

GPT-3、GPT-4 等 LLM 及其开源版本经常难以检索最新信息&#xff0c;有时会产生幻觉或不正确的信息。 检索增强生成 (RAG)是一种将 LLM 的强大功能与外部知识检索相结合的技术。RAG 使我们能够将 LLM 响应建立在事实、最新的信息之上&#xff0c;从而显著提高 AI 生成内容的准…

java基础:流程控制

一、用户交互Scanner &#xff08;一&#xff09;基础 1、概念&#xff1a;基本语法中我们并没有实现程序和人的交互&#xff0c;但是Java给我们提供了这样一个工具类&#xff0c;我们可以获取用户的输入。java.util.Scanner 是 Java5的新特征&#xff0c;我们可以通过Scanne…

MySQL安装与环境配置

1.打开安装程序 2.默认配置&#xff0c;如下二三图 3.配置密码 4.等待安装完毕 5.检查 6.配置环境变量 7.从控制台登录检测

Let‘s Encrypt 申请免费 SSL 证书(每隔60天自动更新证书)

文章目录 官网文档简介安装 Nginxacme.sh生成证书智能化生成证书 安装证书查看已安装证书更新证书 官网 https://letsencrypt.org/zh-cn/ 文档 https://letsencrypt.org/zh-cn/docs/ 简介 Let’s Encrypt 是一个非营利组织提供的免费SSL/TLS证书颁发机构&#xff0c;旨在促…

Vue2和Vue3的区别Vue3的组合式API

一、Vue2和Vue3的区别 1、创建方式的不同&#xff1a; &#xff08;1&#xff09;、vue2:是一个构造函数&#xff0c;通过该构造函数创建一个Vue实例 new Vue({})&#xff08;2&#xff09;、Vue3:是一个对象。并通过该对象的createApp()方法&#xff0c;创建一个vue实例。 Vue…

X86 +PC104+支持WinCE5.0,WinCE6.0,DOS,Win2000,WinXP, Linux,QNX等操作系统,工业控制板卡定制

Celeron N2807 PC104模块 规格产品类型PC/104 主板芯片组SOCCPUIntel Celeron N2807 1.58 GHz内存板载2GB DDR3L双通道内存BIOSAMI 显示 L V D S 18/24-bit&#xff0c;VGA L V D S 支持最大分辨率为 1366768&#xff0c;VGA 支持最大分辨率为20481024 支持双独立显示存储 1Min…

软考《信息系统运行管理员》-2.3信息系统运维的外包

2.3信息系统运维的外包 信息系统运维外包的概念/模式 也称为信息系统代维。是指信息系统使用单位将全部或一部分的信息系统维护服务工作&#xff0c;按照规定的维护服务要求&#xff0c;外包委托给专业公司管理。 完全外包运维模式部分外包模式 信息系统运维外包的好处 有利…

告别PS修图,设计师都在用的AI抠图工具

引言 大家好&#xff01;如果你是美工或设计师&#xff0c;肯定深知Photoshop修图的繁琐和耗时。现在有一款超方便的工具&#xff0c;让你摆脱这些问题——千鹿设计助手。它不仅是个抠图工具&#xff0c;还能通过先进的AI技术&#xff0c;让抠图变得简单快速&#xff0c;让你专…

向量数据库:faiss的常用三种数据索引方式(IndexFlatL2,IndexIVFFlat,IndexIVFPQ)的使用和持久化+索引融合的实现及库函数解读

常用的三种索引方式 Faiss 中有常用的三种索引方式&#xff1a;IndexFlatL2、IndexIVFFlat 和 IndexIVFPQ。 1.IndexFlatL2 - 暴力检索L2&#xff1a; 使用欧氏距离&#xff08;L2&#xff09;进行精确检索。适用于较小规模的数据集&#xff0c;采用暴力检索的方式&#xff0…

基于YOLOv5的人脸关键点检测(附代码)

人脸关键点检测项目说明 本项目的实现主要依靠两个算法&#xff1a;yolov5目标检测和resnet人脸关键点算法。 其中目标检测算法为人脸关键点检测算法的前置算法&#xff0c;使用目标检测算法将人脸信息进行提取(起到前景与背景的分离)&#xff0c;然后再对box内的人脸信息进行…

AI人才争夺战:巨头眼中的产品经理必备技能

前言 在人工智能的浪潮下&#xff0c;BAT等一线互联网企业纷纷加码布局&#xff0c;对AI领域的人才需求空前高涨。然而&#xff0c;要在众多求职者中脱颖而出&#xff0c;成为企业眼中的人才&#xff0c;不仅需要深厚的产品功底&#xff0c;更要具备对AI的深刻理解和应用能力。…

【微信小程序开发实战项目】——如何制作一个属于自己的花店微信小程序(2)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…