当前位置: 首页 > news >正文

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1.用户模块改善

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

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

//原来的实体类属性@NotNullprivate Integer id;//主键IDprivate String username;//用户名@JsonIgnoreprivate String password;//密码@NotEmpty@Pattern(regexp = "^\\S{1,10}$")private String nickname;//昵称@NotEmpty@Emailprivate 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("用户名已被占用");}}@Overridepublic void register(String username, String password) {//加密String md5String = Md5Util.getMD5String(password);//添加userMapper.add(username,md5String);}@Overridepublic 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);}@Overridepublic 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这些方式。)

    @Overridepublic 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;@Overridepublic 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/6224static 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 = "cetide-1325039295";// 创建 PutObjectRequest 对象InputStream inputStream = file.getInputStream();PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, inputStream, null);// 执行上传操作PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);// 获取文件的 URLURL 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 => User3Map<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.利用算法实现了用户之间的匹配机制

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 探索AWS免费资源:无账号也能体验云计算魅力
  • 网络协议七 应用层 DNS协议 和 DHCP协议 这两个都了解就好
  • ffmpeg的基础命令
  • 基于IMX8M_plus+FPGA+AI监护仪解决方案
  • STM32cubeMX配置Systick的bug
  • js原生模板引擎
  • 浅看MySQL数据库
  • 面试题 08.01. 三步问题
  • PULLTYPE
  • MoExtend: 模态和任务扩展调整的新专家
  • Windows安装mmdet3d v0.17.1(跑通版)
  • Linux-零拷贝技术
  • glm4-9B-chat,使用提示工程激活模型最大潜力
  • AR 眼镜之-开关机定制-实现方案
  • java语言中的websocket
  • JavaScript 如何正确处理 Unicode 编码问题!
  • [NodeJS] 关于Buffer
  • 03Go 类型总结
  • 11111111
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • linux安装openssl、swoole等扩展的具体步骤
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • Webpack入门之遇到的那些坑,系列示例Demo
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 来,膜拜下android roadmap,强大的执行力
  • 模仿 Go Sort 排序接口实现的自定义排序
  • 区块链分支循环
  • 我这样减少了26.5M Java内存!
  • 移动端唤起键盘时取消position:fixed定位
  • 树莓派用上kodexplorer也能玩成私有网盘
  • #162 (Div. 2)
  • (C#)一个最简单的链表类
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (每日一问)操作系统:常见的 Linux 指令详解
  • (区间dp) (经典例题) 石子合并
  • (十五)使用Nexus创建Maven私服
  • (实战篇)如何缓存数据
  • (算法)前K大的和
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (转载)深入super,看Python如何解决钻石继承难题
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • .NET Micro Framework 4.2 beta 源码探析
  • .Net OpenCVSharp生成灰度图和二值图
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [20170728]oracle保留字.txt
  • [AAuto]给百宝箱增加娱乐功能
  • [Android Studio 权威教程]断点调试和高级调试
  • [BZOJ1040][P2607][ZJOI2008]骑士[树形DP+基环树]
  • [C#学习笔记]注释
  • [c]扫雷