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

这一次来一个项目改造的项目实战,基于很久之前的一个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.利用算法实现了用户之间的匹配机制

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要实战下载一个基于Spring Boot和Vue 3的大型前后端分离项目,可以按照以下步骤进行: 1. 确保你已经安装了相关的开发环境,包括Java开发环境和Node.js环境。可以在官方网站上下载并安装最新版本的JDK和Node.js。 2. 打开终端或命令行界面,并通过npm安装Vue CLI(Vue Command Line Interface)。在终端中运行以下命令: ``` npm install -g @vue/cli ``` 3. 创建一个新的Vue 3项目。在终端中运行以下命令: ``` vue create my-project ``` 根据提示选择使用Vue 3版本和一些其他配置选项。 4. 进入项目目录。在终端中运行以下命令: ``` cd my-project ``` 5. 开发前端。通过Vue CLI提供的开发服务器,在本地运行前端项目并进行开发。在终端中运行以下命令: ``` npm run serve ``` 这将启动开发服务器,并提供一个本地地址,例如:http://localhost:8080。在浏览器中打开此地址,你将看到默认生成的Vue欢迎页面。 6. 下载Spring Boot后端项目。可以在GitHub等代码托管平台上搜索和下载基于Spring Boot的后端项目模板。选择一个合适的项目,下载并解压缩。 7. 在IDE(如IntelliJ IDEA)中打开后端项目。将此项目导入你的IDE,并按照需要进行配置和修改。 8. 运行后端项目。通过IDE的运行按钮或在终端中运行以下命令启动后端项目: ``` ./mvnw spring-boot:run ``` 后端项目将启动,并监听默认端口8080。 9. 在Vue前端项目中配置跨域访问。由于前后端分离,前端将运行在不同的域上,需要配置后端允许跨域访问。在Vue项目的src目录下创建一个vue.config.js文件,并在其中添加以下代码: ```javascript module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', ws: true, changeOrigin: true } } } } ``` 10. 在前端项目中发起API请求。根据后端项目的API文档,在前端项目的代码中编写对后端接口的调用。可以使用Vue提供的axios等HTTP库来发送请求。 通过以上步骤,你就可以开始在本地开发一个基于Spring Boot和Vue 3的大型前后端分离项目实战。根据实际需求,你可以进一步完善前后端的交互和功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值