vue+elementui+springboot前后端分离实现学校帖子网站,模拟“淘柳职”学校大作业

news/2024/5/20 15:47:44/文章来源:https://yxsdgz.blog.csdn.net/article/details/128536112

一.技术实现

项目演示地址:可私聊作者获取(演示地址不定时变化)

前端

  1. vue+elementui;

后端:

  1. SpringBoot
  2. OAuth2
  3. Spring Security
  4. Redis
  5. mybatis-plus
  6. mysql
  7. swagger

二.前言

淘柳职网站:淘柳职

本项目完全是模拟淘柳职,在此基础上进一步完善和改进,同时也增加了额外的功能,无论是前端功能和样式,除了仿照基本功能外,还在兼容性和样式、细节加以了更多的优化,所花费的时间也是非常大的,从数据库表设计、后端搭建、前端搭建等,足足花费了一周的时间。

可能细节无法一一细说,毕竟这是一个完整的项目,如需更多完整信息,可留言或私聊,马上回复!

三.成品效果对比

1.淘柳职的登录+注册:

2.作者的登录+注册:

主要是作者觉得他的登录样式有点老套洋气,所以作者自己设计了一个:

 登录页源码:

<template><div id="body"><div style="display: flex;width: 100%;height: 100%;overflow: hidden;"><div class="login-modal"><div class="title">{{loginType === 'login'?'登录':(loginType==='forget'?'重置密码':'注册')}}</div><el-form class="login-form":rules="loginRules"ref="loginForm":model="loginForm"label-width="0"><el-form-item prop="phone"><el-inputplaceholder="请输入手机号"prefix-icon="el-icon-mobile-phone"v-model.number="loginForm.phone"clearable></el-input></el-form-item><el-form-item prop="password"><el-input:type="passwordType"placeholder="请输入密码"prefix-icon="el-icon-lock"v-model="loginForm.password"clearable></el-input></el-form-item><el-form-item prop="confirmPassword" v-show="loginType === 'register'"><el-input:type="passwordType"placeholder="请再次输入密码"prefix-icon="el-icon-lock"v-model="loginForm.confirmPassword"clearable></el-input></el-form-item><el-form-item><el-row :span="24"><el-col :span="12"><el-checkbox v-model="loginForm.rememberPwd">记住密码</el-checkbox></el-col><el-col :span="12"><el-popoverplacement="top-start"title=""width="200"trigger="hover"content="忘记密码请联系系统管理员"><span style="color: #1890ff;float: right;" slot="reference">忘记密码</span></el-popover></el-col></el-row></el-form-item><el-form-item><el-button :type="loginType === 'login'?'success':'danger'"style="width: 100%;"@click.native.prevent="handleLogin"class="login-submit">{{loginType === 'login'?'登录':(loginType==='forget'?'重置密码':'注册')}}</el-button></el-form-item><div v-if="loginType === 'login'" style="text-align: center;font-size: 14px;">没有账号?<span style="cursor: pointer;color: #df1f20;" @click="changeModalType('register')">免费注册</span></div><div v-if="loginType !== 'login'" style="text-align: center;font-size: 14px;">已有账号?<span style="cursor: pointer;color: #df1f20;" @click="changeModalType('login')">返回登录</span></div></el-form></div></div></div>
</template><script>import {userRegister} from '@/api/login';export default {name: "index",data() {return {loginType:'login',passwordType: "password",loginForm: {phone: "",password: "",confirmPassword: "",rememberPwd: false,},loginRules: {phone: [{required: true, message: "请输入手机号", trigger: "change"},{ type: 'number', message: '手机号格式错误',trigger: "blur"}],password: [{required: true, message: "请输入密码", trigger: "change"},{min: 6, message: "密码长度最少为6位", trigger: "blur"}],confirmPassword: [{required: false, message: "请再次输入密码", trigger: "change"},{min: 6, message: "密码长度最少为6位", trigger: "blur"}]},};},mounted() {},methods: {homePage(){this.$router.push({path: '/index'});},showPassword() {this.passwordType === ""? (this.passwordType = "password"): (this.passwordType = "");},changeModalType(type){this.loginType = type;this.$refs.loginForm.resetFields();if(type === 'login'){this.loginRules['confirmPassword'][0]['required'] = false;}else{this.loginRules['confirmPassword'][0]['required'] = true;}},handleLogin() {if(this.loginType === 'login'){this.login();}else if(this.loginType === 'register'){this.register();}},login() {//登录this.$refs.loginForm.validate(valid => {if (valid) {const loading = this.$loading({lock: true,text: '登录中,请稍后。。。',spinner: "el-icon-loading"});this.$store.dispatch('login',this.loginForm).then((res)=>{if(res.code === 200){this.$notify({title: '登录成功',message: res.data.nickname+',欢迎您!',type: 'success'});this.$router.push({path: '/'});}}).finally(() =>loading.close());}});},register() {//注册this.$refs.loginForm.validate(valid => {if (valid) {const loading = this.$loading({lock: true,text: '注册中,请稍后。。。',spinner: "el-icon-loading"});userRegister(this.loginForm).then(res => {if(res.code === 200){this.$notify({title: '注册成功',message: '请登录',type: 'success'});}}).finally(() =>loading.close())}});},}}
</script><style scoped>#body{margin: 0;padding: 0;width: 100%;height: 100%;background-size: 100% 100%;background-image: linear-gradient(to top, rgba(255, 95, 45, 0.27), rgba(211, 155, 5, 0.2)), url("../../../public/img/login-bg.png");background-repeat: no-repeat;}.name{line-height: 50px;font-size: 30px;font-weight: 700;color: #FFFFFF;margin-left: 10px;}.login-modal{position: relative;width: 420px;height: 450px;margin: 0 auto;top: 50%;margin-top: -225px;background-color: #FFFFFF;border-radius: 5px;}.title{height: 80px;line-height: 100px;font-weight: 600;text-align: center;font-size: 25px;}.login-form{margin: 20px 40px;}
</style>

3.首页帖子

与淘柳职相比,作者设计的增加了分页和底部子模块(没有分页怎么可以呢,性能无法把控~),其他几乎一样,对了,右上角也增加了昵称展示

 4.详情

与淘柳职相比,大图展示尽量保持图片原有的分辨率,然后右边详细描述文字使用了textarea只读文本框,为了保持与发布帖子输入的内容样式一致,同时增加了关注发帖用户功能(如果是自己的帖子,关注按钮会屏蔽),同时也增加了浏览次数更新,停留页面2秒钟就会浏览次数+1;

5.校园分享

校园分享几个tab使用同一个页面,因为只是查询条件不一样,与淘柳职相比,作者这里也是增加了分页(往下滚动分页,技术采用 Element - The world's most popular Vue UI framework),这里滚动分页放在tab父级页面,作者也是花费了挺长时间来调试实现

tab源码:

<template><div class="body" v-infinite-scroll="loadFun"><div style="font-size: 14px;margin: 0 320px;"><div class="share-tab"><el-tabs v-model="activeName" @tab-click="handleClick"><el-tab-pane label="全部" name="first"><all ref="first"></all></el-tab-pane><el-tab-pane label="官塘校区" name="second"><all ref="second"></all></el-tab-pane><el-tab-pane label="社湾校区" name="third"><all ref="third"></all></el-tab-pane><el-tab-pane label="我的分享" name="fourth"><all ref="fourth"></all></el-tab-pane></el-tabs></div></div></div>
</template><script>import all from "./all.vue";export default {components: {all,},data() {return {activeName: 'first',school: null};},mounted() {this.$refs[this.activeName].init(this.school);},methods: {handleClick(tab, event) {if(tab.index !== '0'){this.school = tab.index;}else{this.school = null;}this.$refs[tab.name].init(this.school);console.log(tab, event);},loadFun() {this.$refs[this.activeName].load();},}};
</script><style>.body{margin: 0;padding: 20px 0 0 0;width: 100%;height: calc(100% - 80px);/*background-size: 100% 100%;*//*background-image:  url("../../../public/img/background-detail.jpg");*//*background-repeat: no-repeat;*//*overflow: hidden;*/background-image: url("../../../public/img/background-detail.jpg");background-size: cover;background-attachment: fixed;overflow: scroll;}/*.share-tab{*//*  overflow: scroll;*//*}*//*.share-tab .el-tabs__content{*//*  overflow: scroll!important;*//*}*/.share-tab .el-tabs__header{background-color: #ffffff!important;padding: 0px 20px!important;}.share-tab .el-tabs__nav{height: 60px!important;line-height: 60px!important;}
</style>

6.关于我们,自由发挥,不重要

7.发布帖子

 样式也是与淘柳职几乎一样,作者没有使用淘柳职发布过,但是作者这里有一点是特别实现的,就是图片上传功能,一般偷懒的做法就是选择图片后马上就会上传到服务器,然后服务器返回图片路径,但是作者很抗拒这种做法,所以作者是选择图片后不会立马提交到服务器,只有点击“立即发布”最后一步才会一起提交到服务器(这样做的好处是不会乱上传图片文件,不会乱占用服务器资源和造成过多垃圾图片)

后端指定上传目录映射:

 8.个人中心

与淘柳职几乎一样,增加了关注数量:

信息修改,回显头像及其他信息: 

 9.我的收藏

淘柳职的 - 我的收藏

作者设计的 - 我的收藏(作者当然是觉得自己的更好看一点,哈哈~),同时增加点击跳转详情功能

 10.我的关注

与淘柳职相比,这是作者自己额外设计实现的功能 

11.我的粉丝

 与淘柳职相比,这也是作者自己额外设计实现的功能 

四.数据表mysql

-- 2022-10-19 用户信息
CREATE TABLE `user_info`
(`id` bigint NOT NULL COMMENT '主键',`nickname` varchar(10) NOT NULL COMMENT '用户名称',`phone` varchar(20) NOT NULL COMMENT '手机号',`password` varchar(255) NOT NULL COMMENT '登录密码 加密',`original_password` varchar(255) NOT NULL COMMENT '登录密码 明文密码',`avatar` varchar(225) DEFAULT NULL COMMENT '头像',`gender`  TINYINT(1) DEFAULT 0 COMMENT '性别 0保密 1男 2女',`status`  TINYINT(1) DEFAULT 0 COMMENT '是否禁用 0否 1是',`hobby` varchar(225) DEFAULT NULL COMMENT '爱好',`remark` varchar(225) DEFAULT NULL COMMENT '备注',`deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除标记 是否已删除: 0否  1是',`create_time` datetime(0) COMMENT '创建时间',`update_time` datetime(0) COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `phone` (`phone`) USING BTREE
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4 COMMENT ='用户信息';-- 首页轮播图CREATE TABLE `banner`
(`id` bigint NOT NULL COMMENT '主键',`img_path` varchar(500) DEFAULT NULL COMMENT '图片路径',`sort`  INT(11) NOT NULL DEFAULT 0 COMMENT '排序',`remark` varchar(225) DEFAULT NULL COMMENT '备注',`status`  TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否禁用 0否 1是',`deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记 是否已删除: 0否  1是',`create_time` datetime(0) COMMENT '创建时间',`update_time` datetime(0) COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4 COMMENT ='首页轮播图';-- 用户关注CREATE TABLE `follow`
(`id` bigint NOT NULL COMMENT '主键',`user_id` bigint NOT NULL COMMENT '用户主键',`be_followed_user_id` bigint NOT NULL COMMENT '被关注用户主键',`status` tinyint(1) DEFAULT 0 COMMENT '是否已读 0否  1是',`deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标记 是否已删除: 0否  1是',`create_time` datetime(0) COMMENT '创建时间',`update_time` datetime(0) COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `user_id_be_followed_user_id` (`user_id`,`be_followed_user_id`) USING BTREE,KEY `user_id` (`user_id`) USING BTREE,KEY `be_followed_user_id` (`be_followed_user_id`) USING BTREE
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4 COMMENT ='用户关注';-- 帖子CREATE TABLE `posts`
(`id` bigint NOT NULL COMMENT '主键',`user_id` bigint NOT NULL COMMENT '帖子所属用户主键',`posts_type`  TINYINT(1) NOT NULL COMMENT '帖子类型 1闲置帖 2校园帖',`title` varchar(225) NOT NULL COMMENT '标题',`content` varchar(1000) DEFAULT NULL COMMENT '内容',`school`  TINYINT(1) NOT NULL COMMENT '校区 1官塘校区 2社湾校区',`price` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '单价',`cover_path` varchar(500) DEFAULT NULL COMMENT '封面图片',`img_path` varchar(2000) DEFAULT NULL COMMENT '图片,多张英文逗号分割',`browse_num` int(11) NOT NULL DEFAULT 0 COMMENT '浏览数量',`collect_num` int(11) NOT NULL DEFAULT 0 COMMENT '收藏数量',`like_num` int(11) NOT NULL DEFAULT 0 COMMENT '点赞数量',`comment_num` int(11) NOT NULL DEFAULT 0 COMMENT '评论数量',`version` int(11) NOT NULL DEFAULT 0 COMMENT '版本号',`status`  TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否禁用 0否 1是',`deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记 是否已删除: 0否  1是',`create_time` datetime(0) COMMENT '创建时间',`update_time` datetime(0) COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4 COMMENT ='帖子';-- 用户收藏CREATE TABLE `collect`
(`id` bigint NOT NULL COMMENT '主键',`user_id` bigint NOT NULL COMMENT '用户主键',`posts_id` bigint NOT NULL COMMENT '帖子主键',`posts_user_id` bigint NOT NULL COMMENT '帖子所属用户主键',`status` tinyint(1) DEFAULT 0 COMMENT '是否已读 0否  1是',`deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标记 是否已删除: 0否  1是',`create_time` datetime(0) COMMENT '创建时间',`update_time` datetime(0) COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `user_id_posts_id` (`user_id`,`posts_id`) USING BTREE,KEY `user_id` (`user_id`) USING BTREE,KEY `posts_id` (`posts_id`) USING BTREE
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4 COMMENT ='用户收藏';-- 帖子点赞CREATE TABLE `posts_like`
(`id` bigint NOT NULL COMMENT '主键',`user_id` bigint NOT NULL COMMENT '用户主键',`posts_id` bigint NOT NULL COMMENT '帖子主键',`posts_user_id` bigint NOT NULL COMMENT '帖子所属用户主键',`status` tinyint(1) DEFAULT 0 COMMENT '是否已读 0否  1是',`deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标记 是否已删除: 0否  1是',`create_time` datetime(0) COMMENT '创建时间',`update_time` datetime(0) COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `user_id_posts_id` (`user_id`,`posts_id`) USING BTREE,KEY `user_id` (`user_id`) USING BTREE,KEY `posts_id` (`posts_id`) USING BTREE
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4 COMMENT ='帖子点赞';-- 帖子评论CREATE TABLE `posts_comment`
(`id` bigint NOT NULL COMMENT '主键',`parent_id` bigint DEFAULT NULL COMMENT '上级评论主键',`parent_user_id` bigint DEFAULT NULL COMMENT '上级评论用户主键',`user_id` bigint NOT NULL COMMENT '用户主键',`posts_id` bigint NOT NULL COMMENT '帖子主键',`posts_user_id` bigint NOT NULL COMMENT '帖子所属用户主键',`content` varchar(1000) DEFAULT NULL COMMENT '评论内容',`status` tinyint(1) DEFAULT 0 COMMENT '是否已读 0否  1是',`deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标记 是否已删除: 0否  1是',`create_time` datetime(0) COMMENT '创建时间',`update_time` datetime(0) COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4 COMMENT ='帖子评论';

五.帖子发布相关后端源码

PostsController:
package com.love.product.controller;import com.love.product.entity.Posts;
import com.love.product.entity.base.Result;
import com.love.product.entity.base.ResultPage;
import com.love.product.entity.req.PostsPageReq;
import com.love.product.entity.req.PostsReq;
import com.love.product.entity.vo.PostsVO;
import com.love.product.service.PostsService;
import com.love.product.util.JwtUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import java.math.BigDecimal;/*** @author hjf* @date 2022-10-19 10:26* @describe 帖子controller*/
@Api(tags = "帖子")
@Slf4j
@RestController
@RequestMapping("/posts")
public class PostsController {@Resourceprivate PostsService postsService;@PostMapping("/add")@ApiOperation(value = "添加", notes = "添加")@ApiImplicitParams({@ApiImplicitParam(name = "postsType", value = "发布类型", required = true, dataType = "Integer", paramType = "query"),@ApiImplicitParam(name = "title", value = "标题", required = true, dataType = "String", paramType = "query"),@ApiImplicitParam(name = "content", value = "内容", required = true, dataType = "String", paramType = "query"),@ApiImplicitParam(name = "school", value = "校区", required = true, dataType = "Integer", paramType = "query"),@ApiImplicitParam(name = "price", value = "校区", required = false, dataType = "BigDecimal", paramType = "query"),@ApiImplicitParam(name = "files", value = "上传图片列表", required = false, dataType = "MultipartFile[]", paramType = "query")})public Result<Posts> add(@RequestParam("postsType")  Integer postsType,@RequestParam("title")  String title,@RequestParam("content")  String content,@RequestParam("school")  Integer school,@RequestParam(value = "price",required = false) BigDecimal price,@RequestParam(value = "files",required = false) MultipartFile[] files) {PostsReq postsReq = new PostsReq();postsReq.setPostsType(postsType);postsReq.setTitle(title);postsReq.setContent(content);postsReq.setSchool(school);postsReq.setPrice(price);postsReq.setFiles(files);return postsService.add(JwtUtil.getUserId(),postsReq);}@ApiOperation("分页")@PostMapping("/getPage")public ResultPage<PostsVO> getPage(@RequestBody PostsPageReq postsPageReq) {return postsService.getPage(JwtUtil.getUserId(),postsPageReq);}@ApiOperation("详情")@GetMapping("/getDetail")public Result<PostsVO> getDetail(@RequestParam("id") Long id) {return postsService.getDetail(JwtUtil.getUserId(),id);}@ApiOperation("浏览")@GetMapping("/browse")public Result<?> browse(@RequestParam("id") Long id) {return postsService.browse(id);}
}
PostsService:
package com.love.product.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.love.product.entity.Posts;
import com.love.product.entity.base.Result;
import com.love.product.entity.base.ResultPage;
import com.love.product.entity.req.PostsPageReq;
import com.love.product.entity.req.PostsReq;
import com.love.product.entity.vo.PostsVO;import java.util.List;
import java.util.Map;/*** @author hjf* @date 2022-10-19 10:26*/
public interface PostsService extends IService<Posts> {Result<Posts> add(Long userId,PostsReq postsReq);ResultPage<PostsVO> getPage(Long userId,PostsPageReq postsPageReq);Result<PostsVO> getDetail(Long userId,Long id);Result<?> browse(Long id);Map<Long, PostsVO> listByIds(List<Long> postsIds);
}
PostsServiceImpl:
package com.love.product.service.impl;import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.love.product.entity.Posts;
import com.love.product.entity.PostsLike;
import com.love.product.entity.base.Result;
import com.love.product.entity.base.ResultPage;
import com.love.product.entity.req.PostsPageReq;
import com.love.product.entity.req.PostsReq;
import com.love.product.entity.vo.PostsVO;
import com.love.product.entity.vo.UserInfoVO;
import com.love.product.enumerate.PostsType;
import com.love.product.enumerate.School;
import com.love.product.enumerate.YesOrNo;
import com.love.product.mapper.PostsMapper;
import com.love.product.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;/*** @author hjf* @date 2022-10-19 10:26*/
@Slf4j
@Service
public class PostsServiceImpl extends ServiceImpl<PostsMapper, Posts> implements PostsService {@Resourceprivate FileUploadService fileUploadService;@Resourceprivate UserInfoService userInfoService;@Resourceprivate CollectService collectService;@Resourceprivate FollowService followService;@Resourceprivate PostsLikeService postsLikeService;/*** 发布帖子*/@Overridepublic Result<Posts> add(Long userId,PostsReq postsReq) {PostsType postsType = PostsType.valueOf(postsReq.getPostsType());School school = School.valueOf(postsReq.getSchool());if(postsType == null){return Result.failMsg("请选择帖子类型");}if(StringUtils.isBlank(postsReq.getTitle())){return Result.failMsg("标题不能为空");}if(StringUtils.isBlank(postsReq.getContent())){return Result.failMsg("内容不能为空");}if(school == null){return Result.failMsg("请选择校区");}List<String> imgPathList = new ArrayList<>();if(postsType.equals(PostsType.LEAVE)){//闲置帖if(postsReq.getPrice() == null || postsReq.getPrice().doubleValue() <= 0){return Result.failMsg("请输入价格");}if(postsReq.getFiles() == null || postsReq.getFiles().length == 0){return Result.failMsg("请至少上传一张图片");}}if(postsReq.getFiles() != null){if(postsReq.getFiles().length > 9){return Result.failMsg("最多可上传9张图片");}//上传图片for(MultipartFile multipartFile : postsReq.getFiles()){String imgPath = fileUploadService.uploadImage(multipartFile);imgPathList.add(imgPath);}}LocalDateTime now = LocalDateTime.now();Posts posts = new Posts();BeanUtil.copyProperties(postsReq,posts);posts.setId(IdWorker.getId());posts.setUserId(userId);posts.setCreateTime(now);posts.setUpdateTime(now);if(imgPathList.size() > 0){posts.setCoverPath(imgPathList.get(0));posts.setImgPath(imgPathList.stream().map(String::valueOf).collect(Collectors.joining(",")));}boolean flag = save(posts);if(flag){return Result.OK("发布成功",posts);}return Result.failMsg("发布失败,请重试");}/*** 分页*/@Overridepublic ResultPage<PostsVO> getPage(Long userId,PostsPageReq postsPageReq) {if(userId == null && Objects.equals(postsPageReq.getSchool(),3)){return ResultPage.FAIL(403,"请登录");}LambdaQueryWrapper<Posts> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(postsPageReq.getPostsType()!=null,Posts::getPostsType, postsPageReq.getPostsType());queryWrapper.eq(postsPageReq.getSchool()!=null&&postsPageReq.getSchool()!=3,Posts::getSchool, postsPageReq.getSchool());queryWrapper.eq(Objects.equals(postsPageReq.getSchool(),3),Posts::getUserId, userId);queryWrapper.eq(Posts::getStatus, YesOrNo.NO.getValue());queryWrapper.orderByDesc(Posts::getCreateTime);Page<Posts> page = page(postsPageReq.build(), queryWrapper);List<PostsVO> list = new ArrayList<>();List<Long> userIds = new ArrayList<>();List<Long> postsIds = new ArrayList<>();if (page.getTotal() > 0) {list = page.getRecords().stream().map(posts -> {PostsVO postsVO = BeanUtil.copyProperties(posts, PostsVO.class);School school = School.valueOf(postsVO.getSchool());postsVO.setSchoolName(school!=null?school.getText():"");initImgPath(postsVO);userIds.add(postsVO.getUserId());postsIds.add(postsVO.getId());return postsVO;}).collect(Collectors.toList());}Map<Long, UserInfoVO> userInfoVOMap;Map<Long, PostsLike> postsLikeHashMap;if(Objects.equals(postsPageReq.getPostsType(),PostsType.SCHOOL.getValue()) && list.size() > 0){userInfoVOMap = userInfoService.listByIds(userIds);postsLikeHashMap = postsLikeService.listByUserId(userId,postsIds);Map<Long, UserInfoVO> finalUserInfoVOMap = userInfoVOMap;Map<Long, PostsLike> finalPostsLikeHashMap = postsLikeHashMap;list.forEach(item -> {UserInfoVO userInfoVO = finalUserInfoVOMap.get(item.getUserId());item.setUserInfo(userInfoVO);item.setLike(false);PostsLike postsLike = finalPostsLikeHashMap.get(item.getId());if(postsLike != null){item.setLike(true);}});}return ResultPage.OK(page.getTotal(), page.getCurrent(), page.getSize(), list);}/*** 详情**/@Overridepublic Result<PostsVO> getDetail(Long userId,Long id) {Posts posts = getById(id);if(posts == null || posts.getStatus().equals(YesOrNo.YES.getValue())){return Result.failMsg("帖子不存在或已下架");}PostsVO postsVO = BeanUtil.copyProperties(posts, PostsVO.class);School school = School.valueOf(postsVO.getSchool());postsVO.setSchoolName(school!=null?school.getText():"");UserInfoVO userInfoVO = userInfoService.getUserInfoById(posts.getUserId());postsVO.setUserInfo(userInfoVO);initImgPath(postsVO);postsVO.setCollect(false);postsVO.setFollow(false);if(userId != null && collectService.getDetail(userId,posts.getId()) != null){postsVO.setCollect(true);}if(userId != null && followService.getDetail(userId,posts.getUserId()) != null){postsVO.setFollow(true);}return Result.OK(postsVO);}/*** 更新浏览次数*/@Overridepublic Result<?> browse(Long id) {Posts posts = getById(id);if(posts != null){posts.setBrowseNum(posts.getBrowseNum() + 1);saveOrUpdate(posts);}return Result.OK();}/*** 拼接图片获取绝对路径*/private void initImgPath(PostsVO postsVO){postsVO.setCoverPath(fileUploadService.getImgPath(postsVO.getCoverPath()));if(StringUtils.isNotEmpty(postsVO.getImgPath())){String[] arr = postsVO.getImgPath().split(",");List<String> list = Arrays.asList(arr);List<String> imgPathList = new ArrayList<>();list.forEach(item-> {imgPathList.add(fileUploadService.getImgPath(item));});postsVO.setImgPath(imgPathList.stream().map(String::valueOf).collect(Collectors.joining(",")));}}/*** 批量获取*/@Overridepublic Map<Long, PostsVO> listByIds(List<Long> postsIds){Map<Long, PostsVO> postsHashMap = new HashMap<>();LambdaQueryWrapper<Posts> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.in(Posts::getId,postsIds);List<Posts> postsList = list(queryWrapper);postsList.forEach(item -> {PostsVO postsVO = BeanUtil.copyProperties(item, PostsVO.class);initImgPath(postsVO);postsHashMap.put(postsVO.getId(), postsVO);});return postsHashMap;}
}

 文件图片上传,存到上面说的路径(D:\school\image\),返回的是相对路径,存储到数据库:

@Overridepublic String uploadImage(MultipartFile file) {if (file == null)throw new BizException("图片不能为空");//得到上传文件的文件名String fileName = file.getOriginalFilename();//以传入的字符串开头,到该字符串的结尾,前开后闭String suffixName = fileName.substring(fileName.lastIndexOf("."));long size = file.getSize();double mul = NumberUtil.div(size, (1024 * 1024), 2);// 自定义异常if (mul > 2)throw new BizException("图片大小不能大于2M");if (!isImage(suffixName))throw new BizException("不是图片格式");// 这里可以用uuid等 拼接新图片名String newFileName = UUID.randomUUID().toString().replace("-", "") + suffixName;// 创建路径String destFileName = fileUploadConfig.getImageRealPath() + File.separator + newFileName;File destFile = new File(destFileName);if (!destFile.getParentFile().exists())destFile.getParentFile().mkdirs();try {//将图片保存到文件夹里file.transferTo(new File(destFileName));} catch (IOException e) {e.printStackTrace();throw new BizException("图片上传错误");}//返回相对路径存储return fileUploadConfig.getImageMapperPath() + newFileName;}

文件在电脑本地D盘,default-avatar: /image/default-avatar.png是用户注册默认的图片,预先放到该目录下

重点是存放在D盘文件图片是如何获取路径让前端显示呢,其实是通过配置映射路径,数据库/image/映射成D:\school\image\,就是以下这一句代码即可,作者放在了swagger配置里面:

 swagger开发文档:

redis下载压缩包解压,启动 redis-server.exe redis.windows.conf:

六.感谢看到这里,还有很多细节无法一一说,毕竟这是一个完整的项目,如需帮助可留言或私聊~,马上回复!

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序猿最应去的网站有哪些?

2019独角兽企业重金招聘Python工程师标准>>> 要想成为优秀的程序猿&#xff0c;不仅要有一定天分&#xff0c;常与大神交流&#xff0c;自己多加练习才是正确的方法。下面是一些Quora用户推荐的国外网站&#xff0c;与广大程序猿或者希望学习编程的朋友们分享&#…

通过建立自己的AuthorizeAttribute实现网站的权限管理

2019独角兽企业重金招聘Python工程师标准>>> 当我们用.net MVC构建网站平台的时候&#xff0c;势必会对网站平台的安全性和用户的使用权限进行一个统一的构建&#xff0c;首先在.net MVC 架构中&#xff0c;系统已经将权限管理分为三个层面来进行管理&#xff0c;第…

delphi RAD Studio新版本及路线图 及官方网站 官方 版本发布时间

delphi RAD Studio Berlin 10.1 主要是FireMonkey 移动开发的改动&#xff0c;VCL确实没有多大变化。 http://docwiki.embarcadero.com/RADStudio/Berlin/en/Main_Page http://docwiki.embarcadero.com/RADStudio/Berlin/en/Whats_New EMB 官网地址资源 Bug fix list for RAD …

网站调查方法步骤.

1.查看对方网站pr值2.查看对方在搜索引擎快照的新鲜度3.到http://whois.domaintolls.com查看对方域名注册信息4.到http://www.archive.org查看对方的收录历史5.查看对方在搜索引擎的收录数6.查看对方的外部链接数7.查看对方是否被雅虎目录&#xff0c;开放目录dmoz.org,好123收…

大型网站架构演变

初级篇&#xff1a;&#xff08;单机模式&#xff09;假设配置&#xff1a;&#xff08;Dual core 2.0GHz,4GB ram,SSD&#xff09;基础框架&#xff1a;apache(PHP) Mysql / IIS MSSQL&#xff08;最基础框架&#xff0c;处理一般访问请求&#xff09;进阶1&#xff1a;替换…

在线流程图与图表制作网站

现在很多功能网站&#xff0c;最常见的就是图片处理类&#xff0c;不如切图&#xff0c;图片转换&#xff0c;添加效果等。本文收集了国外最常用的流程图与图表制作网站&#xff0c;这些网站功能强大&#xff0c;如果你正好需要制作图表或流程图&#xff0c;而对于软件又不熟悉…

linux条件编译预编译,C语言条件编译_Linux编程_Linux公社-Linux系统门户网站

C语言中的预编译包含三种&#xff1a;1.宏定义2.文件包含3.条件编译&#xff0c;条件编译指的是满足一定条件下才进行编译&#xff0c;它有几种形式&#xff1a;(1)#ifdef标识符//程序#else//程序#endif它的意义为如果定义了标识符&#xff0c;则执行程序段1&#xff0c;否则执…

inputstream怎么写给前端_写给大家看的网站制作教程01了解网站制作流程

作者 | 杨小二来源 | web前端开发(ID&#xff1a;webqdkf)前言这些年里&#xff0c;被读者和周围朋友以及一些认识的人&#xff0c;问的最多的问题&#xff0c;就是&#xff0c;怎么做一个自己的网站&#xff1f;这个难不难学呀&#xff1f;其实&#xff0c;我知道&#xff0c;…

Web层框架对网站中所有异常的统一处理

一个网站的异常信息作为专业的人士&#xff0c;是不会轻易暴露给用户的&#xff0c;因为那样狠不安全&#xff0c;显得你漏是一回事&#xff0c;只要还是考虑到网站的数据安全问题&#xff0c;下面给大家分享一下一些常见的web层框架是如何处理统一的异常。 之前都是在Struts2…

修改网站自动关闭时间timeout_centos7修改网卡名称为ethX

测试平台VMWARE WORKSTATION 15 虚拟机系统Centos7centos7网卡的随机名给自动化运维带来混乱&#xff0c;那么我们把网卡名重新配置为ethX一、在安装系统的时候配置&#xff1a;修改内核选项&#xff1a;net.ifnames0 biosdevname0二、已安装系统修改方法像我的虚拟机&#xff…

旧版ios软件网站_ios旧版软件抓包

大家好&#xff0c;这里是小虾虾科技屋——————————————————————有时候&#xff0c;某个软件随着更新&#xff0c;会带走许多历史的痕迹&#xff0c;于是有的人就想尝试复原那些软件&#xff0c;所以产生了今天为大家分享的工具。iTunes旧版软件抓包神器&a…

seo按天扣费系统源码_企业SEO外包,这样选靠谱!

原标题&#xff1a;企业SEO外包&#xff0c;这样选靠谱&#xff01;一般在选择企业SEO外包的时候&#xff0c;需要注意什么点?一、清晰企业网站SEO优化定位清晰企业网站SEO优化定位对于企业的的目标定位&#xff0c;大家说法不一&#xff0c;但是总体上作为企业的负责任你需要…

突发!32TB Windows 10核心数据泄漏,被人上传至第三方公开网站

本文讲的是突发&#xff01;32TB Windows 10核心数据泄漏&#xff0c;被人上传至第三方公开网站&#xff0c;据外媒The Register报道&#xff0c;微软Windows操作系统内部大量组件和核心代码泄漏&#xff0c;正在网络上传播。 这些泄漏的数据多达32TB&#xff0c;包括微软未公开…

Python Django搭建网站流程图解

更多编程教程请到&#xff1a;菜鸟教程 https://www.piaodoo.com/ 友情链接&#xff1a; 高州阳光论坛https://www.hnthzk.com/人人影视http://www.sfkyty.com/1. 创建Django REST framework工程 1.1手动创建工程文件夹 1.2进去工程文件夹内&#xff0c;执行命令&#xff1…

前台页面当前时间_wordpress网站开发中,怎样不同页面调用不同的标题?2招帮你搞定...

一个wordpress网站&#xff0c;它的前台页面是千变万化的&#xff0c;不同的页面肯定有不同的内容和标题。如果一个wordpress网站&#xff0c;所有的页面都是同一个标题&#xff0c;那对于搜索引擎来说&#xff0c;是非常不友好的。那么&#xff0c;在wordpress网站主题模板开发…

xml能存图片吗_robots文件只能放在网站根目录吗?

要使得网站能被百度收录特定页面&#xff0c;robots文件发挥着重要作用&#xff0c;甚至一定层面上影响着网站优化和SEO策略。那么robots文件是不是一定要放在网站的根目录下面呢&#xff1f;【Robots文件的作用】一般搜索引擎蜘蛛会优先访问网站内部的robots文件&#xff0c;根…

Windows 服务器配置、运行、图文流程(新手必备!) - IIS建站配置一条龙

Window 2008 服务器的配置教程本文提供全流程&#xff0c;中文翻译。 Chinar 坚持将简单的生活方式&#xff0c;带给世人&#xff01;&#xff08;拥有更好的阅读体验 —— 高分辨率用户请根据需求调整网页缩放比例&#xff09; Chinar —— 心分享、心创新&#xff01;助力快…

百度搜索结果 转换_百度继续调整搜索结果页面 恢复原本被隐藏的网站地址

来源&#xff1a;蓝点网网址是用户分辨是否是百家号内容的关键参考因素&#xff0c;因此百度这次调整也继续被用户炮轰认为百度居心不良。现在百度再次调整搜索结果页将网站的网址恢复显示&#xff0c;如果本身已关联百度百家或者熊账号则显示媒体名称。部分关键词搜索结果如下…

简单的物流管理网站制作源码_这个网站可以免费DIY专属的圣诞老人问候视频,效果不错制作简单!...

圣诞节快到了&#xff0c;过节气氛越来越浓。。。圣诞节是孩子们最喜爱的节日之一&#xff0c;除了收到圣诞老人送的礼物&#xff0c;如果还能收到来自圣诞老人的专属祝福视频也会是一件能让孩子们非常开心的事情&#xff01;今天给大家推荐的这个网站&#xff0c;可以在家免费…