健韵坊(详细项目实战一)Spring系列 + Vue3

news2025/2/5 15:52:10

这一次来一个项目改造的项目实战,基于很久之前的一个demo项目,来实现一个改造优化和部署上线的项目实战。(就当是接手*山项目并且加以改造的一个实战吧。)

之前是一个关于运动的一个项目(其实之前连名字都没想好hhhh)。

那么先来梳理一下后端的项目已有的东西吧

很经典的项目结构,这个项目主要包括了运动的文章和文章分类,以及用户模块三个。(比较简单的一种)

那么现在来规划一下需求(需要填充的模块):

1.视频的上传,查看,点赞,评论,收藏

2.用户的个人模块的完善(包括常见的权限,增删改查这样几个)

3.手机号,邮箱的登录注册。

4.动态关注,消息提醒这些

5.使用爬虫技术,加上定时任务长期获取数据源

6.匹配系统,匹配相似度高的用户

emmmm,目前就实现这些吧,先把基本的功能完善一下吧:

(让大家能够更快融入,我尽量按照模块来讲解,并且会放上初始项目代码(其实感觉都差不多,估计改完也跟原来没啥相似度了))

1.用户模块改善

先从有基础的用户模块开始吧。

先来改良一下用户User的实体类吧

//原来的实体类属性
    @NotNull
    private Integer id;//主键ID
    private String username;//用户名
    @JsonIgnore
    private String password;//密码
    @NotEmpty
    @Pattern(regexp = "^\\S{1,10}$")
    private String nickname;//昵称
    @NotEmpty
    @Email
    private String email;//邮箱
    private String userPic;//用户头像地址
    private LocalDateTime createTime;//创建时间
    private LocalDateTime updateTime;//更新时间

之前的算是比较简单的一个,那么现在来分析一下 

功能实现:快速开发操作:使用MybatisX进行快速生成

这边建议是速度开发的话可以使用MybatisX进行快速开发(简化一些不必要的操作)

这样的话就只需要关注目前的数据库表的字段情况了

 

开始修改user表

目前来看只有最基础的几个,那么现在来添加一些业务必要的字段:

在数据库中添加字段,然后右击使用MybatisX进行代码自动生成就可以了(这里不建议使用lombok,之前出过错)

 好,先来来梳理一下我们要设计的接口有哪些:

1.用户根据账号密码登录
2.用户根据邮箱实现登录
3.用户根据手机号实现登录

4.用户注册

5.获取当前用户信息
6.根据用户名查询用户信息
7.修改当前用户的信息
8.判断当前用户是否拥有权限
9.修改用户密码

 目前就是这些了。

1.注册的原始代码:

    @PostMapping("/register")
    public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {

        //查询用户
        User u = userService.findByUserName(username);
        if (u == null) {
            //没有占用
            //注册
            userService.register(username, password);
            return Result.success();
        } else {
            //占用
            return Result.error("用户名已被占用");
        }
    }

        @Override
    public void register(String username, String password) {
        //加密
        String md5String = Md5Util.getMD5String(password);
        //添加
        userMapper.add(username,md5String);
    }
    
        @Override
    public User findByUserName(String username) {
        User u = userMapper.findByUserName(username);
        return u;
    }

emmmm,确实简陋。

那么现在来改造一下

这边尽量就在controller层接收请求,逻辑处理的都放在service层。

注册的代码比较简单总体来说也就是格式校验,查看数据库中是否有此人已经注册,然后创建新的用户,给用户的一些属性赋初值存储数据库。

    @PostMapping("/register")
    public Result register(@Validated UserRegisterByEmail userRegister, HttpServletRequest request) {
        return userService.register(userRegister,request);
    }
        @Override
    public Result register(UserRegisterByEmail userRegister, HttpServletRequest request) {
        String registerIp = getIpAddr(request);
        String username = userRegister.getUsername();
        String password = userRegister.getPassword();
        String email = userRegister.getEmail();
        //校验邮箱和用户名是否已经被注册
        if (userMapper.findByUserName(username) != null) {
            return Result.error("用户名已被注册");
        }
        if (userMapper.findByEmail(email) != null) {
            return Result.error("邮箱已被注册");
        }
        //校验通过
        User user = new User();
        user.setEmail(email);
        user.setUsername(username);
        user.setPassword(Md5Util.getMD5String(password));
        user.setNickname(USER_REGISTER_NAME + UUID.randomUUID());
        user.setUserPic(USER_REGISTER_PIC);
        user.setIsActivated(1);
        user.setLastLoginIp(registerIp);
        int insert = userMapper.save(user);
        if (insert > 0) {
            return Result.success();
        } else {
            return Result.error("注册失败");
        }
    }

然后实现以下QQ邮箱的登录方案:(这个之前说过)

        1.引入依赖

<dependency>
 <groupId>javax.mail</groupId>
 <artifactId>mail</artifactId>
 <version>1.4</version>
</dependency>

        2.创建EmailUtils编写发送邮件的工具方法

package com.sportback.utils;
import java.security.Security;
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
public class EmailUtils {
    public static void sendEmail(String to, String subject, String content) {
        try {
            final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";

            //配置邮箱信息
            Properties props = System.getProperties();
            //邮件服务器
            props.setProperty("mail.smtp.host", "smtp.qq.com");
            props.setProperty("mail.smtp.socketFactory.class", SSL_FACTORY);
            props.setProperty("mail.smtp.socketFactory.fallback", "false");
            //邮件服务器端口
            props.setProperty("mail.smtp.port", "465");
            props.setProperty("mail.smtp.socketFactory.port", "465");
            //鉴权信息
            props.setProperty("mail.smtp.auth", "true");
            //建立邮件会话
            Session session = Session.getDefaultInstance(props, new Authenticator() {
                //身份认证
                protected PasswordAuthentication getPasswordAuthentication() {
                    //1.账户 授权码
                    return new PasswordAuthentication("xxxxxxx@qq.com", "xxxx");
                }
            });
            //建立邮件对象
            MimeMessage message = new MimeMessage(session);
            //设置邮件的发件人
            message.setFrom(new InternetAddress("xxxxxxx@qq.com"));
            //2.设置邮件的收件人
            message.setRecipients(Message.RecipientType.TO, to);
            //设置邮件的主题
            message.setSubject(subject);
            //文本部分
            message.setContent(content, "text/html;charset=UTF-8");
            message.saveChanges();
            //发送邮件
            Transport.send(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 return new PasswordAuthentication("xxxxxxx@qq.com", "xxxx");

   message.setFrom(new InternetAddress("xxxxxxx@qq.com"));

这两个需要设置以下(详细可以参考以下我之前的文章,其实就是QQ邮件的授权码和QQ邮箱)

然后就可以编写接口代码了,一个是接收前端的邮箱进行获取验证码,一个是获取邮箱和验证码进行验证(这里其实还可以进行一个判断是否有这个用户,然后进行快速注册)

这些过于简单的就不细说了,前面的文章也写过。

功能实现:进行定时的一周总结。

之前在使用coding的时候,它会定时推送一个一周总结的东西,总结一周下来的总代码量,完成的需求,修复的bug这些。

那么今天就在用户模块这里模仿出一个这样的功能。

根本上来说是要用定时任务框架,定时自动执行某行Java代码,这里可以使用SpringTask

在原来的包结构下创建一个job包,并创建WeekSummary类

1.引入依赖

//导入Spring-context的jar包
<!--        spring-context-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>

2.启动类添加注解@EnableScheduling开启任务调度

3.自定义定时任务类(编写一个简单类)

package com.sportback.job;


import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class WeekSummary {

    @Scheduled(cron = "0/5 * * * * ?")//执行时间间隔
    public void executeTask(){
        System.out.println("定时任务开始"+new Date());
    }
}

然后就可以根据时间进行筛选:

    @Scheduled(cron = "0/5 * * * * ?")
    public void executeTask(){
        LocalDateTime time  = LocalDateTime.now();
        LocalDateTime lastTime = time.minusDays(7);
        List<UserLog> userLogs = logService.getUserLogsByTime(time,lastTime);
        System.out.println(userLogs);
    }

这里做测试的时候时间间隔就短一些,这里就获取用户在最近七天的登录注册的日志信息吧,获取当前的时间信息,然后调用logService,对近期的数据进行筛查就好了(这里输出出来,就不放到前端了,如果要将这种定时信息放到前端的话,可以使用轮询,websocket这些方式。)

    @Override
    public List<UserLog> getUserLogsByTime(LocalDateTime time, LocalDateTime lastTime) {
        DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return logMapper.getUserLogsByTime(outputFormatter.format(time), outputFormatter.format(lastTime));
    }
    
        @Select("select * from user_logs where timestamp between #{lastTime} and #{time}")
    List<UserLog> getUserLogsByTime(String time, String lastTime);
    

这里写的比较简易,可以结合其他的业务进行总结和数据处理。

功能实现:网站定时获取数据源(爬虫技巧)

对于初始的系统来说需要导入一些数据,这里用一些爬虫的方法,在每日凌晨获取到其他网站的信息试试。

1.分析目的网站的字段,然后根据字段创建实体类。

public class Post implements Serializable {
    /**
     * id
     */
    private Long id;
    /**
     * 标题
     */
    private String title;

    /**
     * 内容
     */
    private String content;
    /**
     * 标签列表 json
     */
    private String tags;

    /**
     * 点赞数
     */
    private Integer thumbNum;

    /**
     * 收藏数
     */
    private Integer favourNum;

    /**
     * 创建用户 id
     */
    private Integer userId;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 是否删除
     */
    private Integer isDelete;

    @Override
    public String toString() {
        return "Post{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", tags='" + tags + '\'' +
                ", thumbNum=" + thumbNum +
                ", favourNum=" + favourNum +
                ", userId=" + userId +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                ", isDelete=" + isDelete +
                '}';
    }

    public Long getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getTags() {
        return tags;
    }

    public void setTags(String tags) {
        this.tags = tags;
    }

    public Integer getThumbNum() {
        return thumbNum;
    }

    public void setThumbNum(Integer thumbNum) {
        this.thumbNum = thumbNum;
    }

    public Integer getFavourNum() {
        return favourNum;
    }

    public void setFavourNum(Integer favourNum) {
        this.favourNum = favourNum;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public Integer getIsDelete() {
        return isDelete;
    }

    public void setIsDelete(Integer isDelete) {
        this.isDelete = isDelete;
    }

2.使用相关的发起网络请求的技术进行爬虫,比如jsoup,hutool工具包,httpClient这些都可以,可以看看之前发的文章,了解一下。

    @Scheduled(cron = "0/5 * * * * ?")
    public void executeTask(){
        //1.获取数据
        String json = "{\n" +
                "  \"current\": 1,\n" +
                "  \"pageSize\": 8,\n" +
                "  \"sortField\": \"createTime\",\n" +
                "  \"sortOrder\": \"descend\",\n" +
                "  \"category\": \"文章\",\n" +
                "  \"tags\": [],\n" +
                "  \"reviewStatus\": 1\n" +
                "}";
        String url = "https:xxxxxxxxxxxx";
        String result2 = HttpRequest.post(url)
                .body(json)
                .execute().body();
        //2.JSON转对象
        Map<String, Object> map = JSONUtil.toBean(result2, Map.class);
        JSONObject data = (JSONObject)map.get("data");
        JSONArray records = (JSONArray)data.get("records");
        List<Post> postList = new ArrayList<>();
        for(Object record : records){
            JSONObject tmpObject = (JSONObject)record;
            Post post = new Post();
            List<String> list = tags.toList(String.class);
            post.setTags(JSONUtil.toJsonStr(list));
            post.setCreateTime(new Date());
            post.setUpdateTime(new Date());
            postList.add(post);
        }
        postService.saveAll(postList);
        System.out.println(result2);
    }

这样就能实现每天进行一次数据的更新了。

功能实现:腾讯云对象存储

引入依赖

<dependency>
     <groupId>com.qcloud</groupId>
     <artifactId>cos_api</artifactId>
     <version>5.6.227</version>
</dependency>

引入上传对象代码

    // 1 传入获取到的临时密钥 (tmpSecretId, tmpSecretKey, sessionToken)
    static String tmpSecretId = "";
    static String tmpSecretKey = "";
    static String sessionToken = "TOKEN";
    static BasicSessionCredentials cred = new BasicSessionCredentials(tmpSecretId, tmpSecretKey, sessionToken);
    // 2 设置 bucket 的地域
    // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分
    static Region region = new Region(""); //COS_REGION 参数:配置成存储桶 bucket 的实际地域,例如 ap-beijing,更多 COS 地域的简称请参见 https://cloud.tencent.com/document/product/436/6224
    static ClientConfig clientConfig = new ClientConfig(region);
    // 3 生成 cos 客户端
    static COSClient cosClient = new COSClient(cred, clientConfig);

        public static String uploadFile(MultipartFile file, String fileName) throws CosClientException, IOException {
        // 指定文件将要存放的存储桶
        String bucketName = "";

        // 创建 PutObjectRequest 对象
        InputStream inputStream = file.getInputStream();
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, inputStream, null);

        // 执行上传操作
        PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);

        // 获取文件的 URL
        URL objectUrl = cosClient.getObjectUrl(bucketName, fileName);

        // 关闭客户端(关闭后台线程)
        cosClient.shutdown();

        return objectUrl.toString();
    }

这样就可以了。

定义删除对象的操作

    public static boolean deleteFile(String fileName) {
        try {
            // 指定要删除的存储桶名称和文件
            String bucketName = "";
            // 指定要删除的存储桶名称和文件
            cosClient.deleteObject(bucketName, fileName);
            // 关闭客户端(关闭后台线程)
            cosClient.shutdown();
            return true;
        } catch (CosClientException cce) {
            cce.printStackTrace();
            return false;
        }
    }

这样就可以实现腾讯云的对象存储了。(注意可以把secretid和key放在配置文件或者数据库中进行存储)

改造一下上传接口

@RestController
public class FileUploadController {

    @PostMapping("/upload")
    public Result<String> upload(@RequestPart MultipartFile file) throws Exception {
        // 生成文件名
        String originalFilename = file.getOriginalFilename();
        String filename = null;
        if (originalFilename != null) {
            filename = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
        }
        // 上传文件
        String publicUrl = CosUtils.uploadFile(file, filename);
        return Result.success(publicUrl);
    }
}

此时通过postman进行测试就没有问题了

理论上这里需要把publicurl返回回来,但是实际上这样是不好的,不建议将自身对象存储的url暴露给前端的,这里后面需要再修改一下(先把功能实现了吧)

功能实现:用户匹配机制

编写工具类进行使用

    public static int minDistance(List<String> tagList1, List<String> tagList2) {
        int n = tagList1.size();
        int m = tagList2.size();

        if (n * m == 0) {
            return n + m;
        }

        int[][] d = new int[n + 1][m + 1];
        for (int i = 0; i < n + 1; i++) {
            d[i][0] = i;
        }

        for (int j = 0; j < m + 1; j++) {
            d[0][j] = j;
        }

        for (int i = 1; i < n + 1; i++) {
            for (int j = 1; j < m + 1; j++) {
                int left = d[i - 1][j] + 1;
                int down = d[i][j - 1] + 1;
                int left_down = d[i - 1][j - 1];
                if (!Objects.equals(tagList1.get(i - 1), tagList2.get(j - 1))) {
                    left_down += 1;
                }
                d[i][j] = Math.min(left, Math.min(down, left_down));
            }
        }
        return d[n][m];
    }

然后在UserController类中使用。

在逻辑处理处进行匹配即可

    public List<User> matchUsers(long num, User loginUser) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "tags");
        queryWrapper.isNotNull("tags");
        List<User> userList = this.list(queryWrapper);
        String tags = loginUser.getTags();
        Gson gson = new Gson();
        List<String> tagList = gson.fromJson(tags, new TypeToken<List<String>>() {}.getType());
        // 用户列表的下标 => 相似度
        List<Pair<User, Long>> list = new ArrayList<>();
        // 依次计算所有用户和当前用户的相似度
        for (int i = 0; i < userList.size(); i++) {
            User user = userList.get(i);
            String userTags = user.getTags();
            // 无标签或者为当前用户自己
            if (StringUtils.isBlank(userTags) || Objects.equals(user.getId(), loginUser.getId())) {
                continue;
            }
            List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {}.getType());
            // 计算分数
            long distance = AlgorithmUtils.minDistance(tagList, userTagList);
            list.add(new Pair<>(user, distance));
        }
        // 按编辑距离由小到大排序
        List<Pair<User, Long>> topUserPairList = list.stream()
                .sorted((a, b) -> (int) (a.getValue() - b.getValue()))
                .limit(num)
                .collect(Collectors.toList());
        // 原本顺序的 userId 列表
        List<Integer> userIdList = topUserPairList.stream()
                .map(pair -> pair.getKey().getId())
                .collect(Collectors.toList());
        Collections.reverse(userIdList); // 使用 Collections.reverse() 方法反转列表
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.in("id", userIdList);
        // 1, 3, 2
        // User1、User2、User3
        // 1 => User1, 2 => User2, 3 => User3
        Map<Integer, List<User>> userIdUserListMap = this.list(userQueryWrapper)
                .stream()
                .collect(Collectors.groupingBy(User::getId));
        List<User> finalUserList = new ArrayList<>();
        for (Integer userId : userIdList) {
            finalUserList.add(userIdUserListMap.get(userId).get(0));
        }
        return finalUserList;
    }

这样其实就可以实现了。

1.视频的上传,查看,点赞,评论,收藏

2.用户的个人模块的完善(包括常见的权限,增删改查这样几个)

3.手机号,邮箱的登录注册。

4.动态关注,消息提醒这些

5.使用爬虫技术,加上定时任务长期获取数据源

6.匹配系统,匹配相似度高的用户

那么今天其实已经实现了:

1.用户模块的完善(其实还差一些)

2.邮箱的登录

3.使用爬虫技术实现了数据的定时更新。

4.使用定时模块实现了相关功能

5.利用算法实现了用户之间的匹配机制

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

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

相关文章

vue3 响应式 API:watch()、watchEffect()

watch() 基本概念 watch()用于监视响应式数据的变化&#xff0c;并在数据变化时执行相应的回调函数。可以监视单个响应式数据、多个响应式数据的组合&#xff0c;或者一个计算属性。 返回值 返回一个函数&#xff0c;调用这个函数可以停止监视。 特点 watch() 默认是懒侦听的&…

【Linux网络】select函数

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 select函数介绍select函数参数介绍select函数返回值select的工作流程TCP服务器【多路复用版】 select函数介绍 在Linux网络编程中&#xff0c;select 函数是一种非常有用的IO多路复用技术&#xff0…

秃姐学AI系列之:LeNet + 代码实现

目录 LeNet MNIST数据集 LeNet模型图 ​编辑 总结 代码实现&#xff1a;卷积神经网络 LeNet LeNet&#xff08;LeNet-5&#xff09;由两个部分组成&#xff1a;卷积编码器核全连接层密集块 检查模型 LeNet 卷积神经网络里面最为著名的一个网络&#xff0c;80年代末提出…

【vue教程】七. Vue 的动画和过渡

文章目录 往期列表回顾本章涵盖知识点Vue 的内置动画系统基本的进入和离开过渡列表过渡 CSS 过渡CSS 过渡基础Vue 中的 CSS 过渡 JavaScript 动画使用 JavaScript 钩子 第三方动画库的使用集成 Animate.css 实例演示创建一个简单的动画应用 结语 往期列表 【vue教程】一. 环境…

iOS18升级出现白苹果、无法重启等问题,要怎么解决

随着苹果iOS 18系统beta版本的推出&#xff0c;不少用户在私信说升级后遇到了白苹果和无法重启等问题。这些问题不仅影响了大家的正常使用&#xff0c;还会导致数据丢失和系统崩溃。本文将详细介绍iOS 18升级后出现白苹果、无法重启等问题的原因及解决方法&#xff0c;帮助大家…

日期转时间濯

tfunction(date_str) local code ,time World:getTimeFromDateString(date_str) return time/(60*60*24) end print(t(2024-08-16)-t(2024-08-3))

指针 (四)

一 . 指针的使用和传值调用 &#xff08;1&#xff09;strlen 的模拟实现 库函数 strlen 的功能是求字符串长度&#xff0c;统计的是字符串中 \0 之前的字符个数&#xff0c;函数原格式如下&#xff1a; 我们的参数 str 接收到一个字符串的起始地址&#xff0c;然后开始统计…

JS 获取当前操作系统类型

在JavaScript中&#xff0c;‌直接获取用户的操作系统信息是不可能的&#xff0c;‌因为JavaScript主要运行在浏览器中&#xff0c;‌而浏览器出于安全和隐私的考虑&#xff0c;‌不会提供访问操作系统详细信息的API。‌ 但是&#xff0c;‌你可以通过分析用户代理字符串&…

数据库性能定位-慢sql定位、sql优化(docker容器实战)

安装好mysql数据之后&#xff0c;创建库的时候&#xff0c;要注意选择 字符集编码。如果没有选择好&#xff0c;你的库表存中文的时候&#xff0c;会字符集乱码。选择utf8mb4. 建表的时候&#xff0c;存储引擎 InnoDB、MyISAM mysql5.7及以后数据库&#xff0c;表的默认存储引…

官方招募 | 仓颉语言三方库社区建设全速启航,全球开发者、技术大神只等您!

Cangjie-TPC招募令 仓颉社区的小伙伴们&#xff0c;官方三方库&#xff08;Cangjie-TPC&#xff09;招募开始啦&#xff01; Cangjie-TPC&#xff08;https://gitcode.com/Cangjie-TPC&#xff09; 是 Cangjie 社区用于汇集基于仓颉编程语言开发的开源三方库的主干仓&#xf…

JS获取当前浏览器名称

在JavaScript中&#xff0c;获取当前浏览器名称的方法并不是一个标准的功能&#xff0c;因为浏览器厂商并没有提供一个直接的API来获取浏览器的名称。但是&#xff0c;你可以通过分析用户代理字符串&#xff08;User-Agent&#xff09;来推断出浏览器的名称。 以下是一个简单的…

ArcGIS如何将投影坐标系转回为地理坐标系

有时候两个数据&#xff0c;一个为投影坐标系&#xff0c;另一个为地理坐标系时&#xff0c;在GIS软件中位置无法叠加到一起&#xff0c;这需要将两个或多个数据的坐标系统一&#xff0c;可以直接将地理坐标系的数据进行投影&#xff0c;或将投影坐标系转为地理坐标系。下面介绍…

视频号AI美女跳舞,轻松月入30000+,蓝海赛道,流量池巨大,起号猛

今天给大家分享的是一个男生比较感兴趣的内容&#xff0c;AI美女视频跳舞项目4.0版本&#xff0c;7天快速起号&#xff0c;实现每月30000的稳定收入. 大家刷抖音的时候&#xff0c;肯定都刷到过美女跳舞的视频&#xff0c;对吧&#xff1f;这种视频&#xff0c;不管在哪个平台…

电商项目DevOps一体化运维实战

主要讲了git和jkins的使用&#xff0c;其中maven的一个插件还挺好用的&#xff0c;主要可以用来查看哪些类没有使用&#xff0c;哪些导入的包是多余的等。这里展示一下用法。至于git和jkins的搭建后续再操作。 maven插件的使用&#xff1a; 编译后就可以在target下面看到这个h…

网络安全之XSS基础

从 XSS Payload 学习浏览器解码 xss payload 1.<a href"%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29">1</a> 2.<a href"&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;:…

代码随想录:动态规划6-10

62、不同路径 题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径…

基于STM32开发的智能农业环境监控系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化传感器数据采集与处理环境控制与状态指示Wi-Fi通信与远程监控应用场景 农业温室环境管理农田环境监控与自动化管理常见问题及解决方案 常见问题解决方案结论 1. 引言 随着农业技术…

Codigger 视频会议(Meeting):制造业的高效协作引擎

在制造业数字化转型的浪潮中&#xff0c;企业面临着前所未有的机遇与挑战。视频会议作为一种高效的沟通工具&#xff0c;在制造业中发挥着关键作用。 制造业对于视频会议有着多方面的需求与挑战。不同生产基地往往分布在各地&#xff0c;跨地域协作需求十分迫切。在技术交流时&…

stm32—ADC

1. 什么是ADC 生活中我们经常会用到ADC这种器件&#xff0c;比如说&#xff0c;当我们在使用手机进行语音通信时&#xff0c;ADC器件会将我们的声信号转换为电信号 (模拟信号 ---> 数字信号) 模拟信号&#xff1a; 模拟信号是指用连续变化的物理量表示的信息&#xff0c;其信…

【Vue3】编程式路由导航

【Vue3】编程式路由导航 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日…