WhiteHole社区实战开发(个人页面展示)

203 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

前言

由于部分原因,导致这个Auto2.0的例子暂时没有办法演示,所以的话,咱们现在就先不管这个直接进行开发。因为咱们的这个User是做好了的,然后由于我们直接使用的是token的方案,所以的话,一方面我们可以为移动端也提供便利,另一方面我们不太需要关系单点登录的问题,其实原因也很简单,人家是中心Session,我们是中心token。那么今天的话我们就来看看这个个人页面展示咱们是怎么做的。那些技术点。

实现效果

我们先来看看我们最终实现的效果。 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 大概就这些,其他的就演示了,都差不多,区别就是咱们的这个数据是由后端拿过来的。

值得一提的是,由于咱们的这个其实也是属于公开的页面,因为别人点击这个头像的话,其实也是可以看到的,所以的话,那么我们就是说这里做的其实就是单纯的一个公开的页面,只需要传入userid就可以访问。但是对于个人管理的页面的话就涉及到token的验证了。那么这个就是咱们后面需要说道说道的内容了,但是现在咱们先不着急,先说说咱们这个。

前端

我们先从前端开始,我们拿两个页面来说明,一个就是在大页面的展示,还有就是咱们文章的展示。

认证存储

这个的话就是单纯的前端去存储我们后端的一些数据,最重要的就是咱们的token之类的信息。那么咱们这边是需要做token的7天存储的,所以的话我们这里有一个优化。就是这样的。


Storage.prototype.setExpire=(key, value, expire) =>{
  let obj={
    data:value,
    time:Date.now(),
    expire:expire
  };
  localStorage.setItem(key,JSON.stringify(obj));
}
//Storage优化
Storage.prototype.getExpire= key =>{
  let val =localStorage.getItem(key);
  if(!val){
    return val;
  }
  val =JSON.parse(val);
  if(Date.now()-val.time>val.expire){
    localStorage.removeItem(key);
    return null;
  }
  return val.data;
}

在这里插入图片描述 这样的话,那么我们就可以很方便的实现存储了,而且在存的时候,我们还默认把object转化为了json字符串。

页面编写

之后我们来看到我们最基本的页面的编写。

<template>
  <div style="width: 90%;margin: 0 auto">
    <vue-particles
      class="login-background"
      color="#97D0F2"
      :particleOpacity="0.7"
      :particlesNumber="200"
      shapeType="circle"
      :particleSize="4"
      linesColor="#97D0F2"
      :linesWidth="1"
      :lineLinked="true"
      :lineOpacity="0.8"
      :linesDistance="150"
      :moveSpeed="3"
      :hoverEffect="true"
      hoverMode="grab"
      :clickEffect="true"
      clickMode="push">
    </vue-particles>
    <el-container style="width: 80%;margin: 0 auto" >

        <div  style="z-index: 1" >
          <div class="show" style="width: 900px;text-align: center">
            <div >
              <div style="width:200px;height: 100%;border-radius: 100px;margin: 0 auto">
                <el-image
                  style="width: 150px;height: 150px;border-radius: 100px;"
                  :src="User.userpic"
                  class="image"
                >
                </el-image>
                <p>
                  {{User.nickname}}
                </p>
              </div>
              <div>
                <br>
                <p>

                  <el-button @click="focusOn"  v-if="focusOnFlag===false" icon="el-icon-plus" type="primary" plain >
                    <span>关注</span>
                  </el-button>
                  <el-button @click="focusOn"  v-else icon="el-icon-check" type="primary" plain >
                    <span>已关注</span>
                  </el-button>
                </p>
              </div>
            </div>
            <div>
              <p style="font-size: larger;font-family: 楷体;text-align: center">简介</p>
              <p style="font-size: smaller;width: 65%;margin: 0 auto">
                 {{User.userinfo}}
              </p>
              <br><br>
            </div>
          </div>
          <div style="width: 30%;margin: 0 auto">
            <el-breadcrumb separator-class="el-icon-arrow-right">
              <el-breadcrumb-item :to="{ path: '/userinfo',query:{'userid':this.User.userid}}">文章</el-breadcrumb-item>
              <el-breadcrumb-item :to="{ path: '/userinfo/userquizList',query:{'userid':this.User.userid}}">提问</el-breadcrumb-item>
              <el-breadcrumb-item :to="{ path: '/userinfo/useransList',query:{'userid':this.User.userid}}">回答</el-breadcrumb-item>
              <el-breadcrumb-item :to="{ path: '/userinfo/userunityList',query:{'userid':this.User.userid}}">TA的社区</el-breadcrumb-item>
              <el-breadcrumb-item ></el-breadcrumb-item>
            </el-breadcrumb>
          </div>
          <div style="width: 100%;margin: 0 auto">
            <router-view></router-view>
          </div>
        </div>
    </el-container>
  </div>
</template>

这个页面的话其实就只是这部分的 在这里插入图片描述 后面的文章,提问之类的其实是另一个页面的,这个我们先不管。

页面缓存

首先后端的话咱们有sentient去限制流量,但是在前端的话,咱们还是要尽可能去避免这个情况的发生。 但是这个页面的缓存的话,咱们肯定是,浏览器关闭后就没了。

那么们这个简单就直接这样。

this.User = JSON.parse(localUser)
sessionStorage.setItem("user", JSON.stringify(this.User));

这里需要注意转换。

完整代码

ok,那么这个时候我们来看一看前端的完整代码

<template>
  <div style="width: 90%;margin: 0 auto">
    <vue-particles
      class="login-background"
      color="#97D0F2"
      :particleOpacity="0.7"
      :particlesNumber="200"
      shapeType="circle"
      :particleSize="4"
      linesColor="#97D0F2"
      :linesWidth="1"
      :lineLinked="true"
      :lineOpacity="0.8"
      :linesDistance="150"
      :moveSpeed="3"
      :hoverEffect="true"
      hoverMode="grab"
      :clickEffect="true"
      clickMode="push">
    </vue-particles>
    <el-container style="width: 80%;margin: 0 auto" >

        <div  style="z-index: 1" >
          <div class="show" style="width: 900px;text-align: center">
            <div >
              <div style="width:200px;height: 100%;border-radius: 100px;margin: 0 auto">
                <el-image
                  style="width: 150px;height: 150px;border-radius: 100px;"
                  :src="User.userpic"
                  class="image"
                >
                </el-image>
                <p>
                  {{User.nickname}}
                </p>
              </div>
              <div>
                <br>
                <p>

                  <el-button @click="focusOn"  v-if="focusOnFlag===false" icon="el-icon-plus" type="primary" plain >
                    <span>关注</span>
                  </el-button>
                  <el-button @click="focusOn"  v-else icon="el-icon-check" type="primary" plain >
                    <span>已关注</span>
                  </el-button>
                </p>
              </div>
            </div>
            <div>
              <p style="font-size: larger;font-family: 楷体;text-align: center">简介</p>
              <p style="font-size: smaller;width: 65%;margin: 0 auto">
                 {{User.userinfo}}
              </p>
              <br><br>
            </div>
          </div>
          <div style="width: 30%;margin: 0 auto">
            <el-breadcrumb separator-class="el-icon-arrow-right">
              <el-breadcrumb-item :to="{ path: '/userinfo',query:{'userid':this.User.userid}}">文章</el-breadcrumb-item>
              <el-breadcrumb-item :to="{ path: '/userinfo/userquizList',query:{'userid':this.User.userid}}">提问</el-breadcrumb-item>
              <el-breadcrumb-item :to="{ path: '/userinfo/useransList',query:{'userid':this.User.userid}}">回答</el-breadcrumb-item>
              <el-breadcrumb-item :to="{ path: '/userinfo/userunityList',query:{'userid':this.User.userid}}">TA的社区</el-breadcrumb-item>
              <el-breadcrumb-item ></el-breadcrumb-item>
            </el-breadcrumb>
          </div>
          <div style="width: 100%;margin: 0 auto">
            <router-view></router-view>
          </div>
        </div>
    </el-container>
  </div>
</template>
<script>
export default {
  name: "userinfo",
  data(){
    return{
      focusOnFlag:false,
      User:{
        userid: null,
        nickname: null,
        userpic: null,
        userinfo: null,
      }
    }
  },
  created() {
  //  我们使用这个来完成对Userinfo信息的一个处理
  //  先拿到用户的userid,当链接跳转过来的时候
  //  过来先判断一下有没有携带userid,如果有的话就直接查询,那个userid
  //  如果没有的话,那么判断一下本地有没有userid,如果有那么说明是用户查看自己的一个Userinfo
  //  反之都没有的话,那么就说明这个可能是非法进入
    let userid = this.$route.query.userid;
    if(!userid){
      let localUserid = localStorage.getExpire("userid")
      if(!localUserid){
        alert("检测到您未登录,请先登录")
        this.$router.push({path: "/login"});
      }else {
        userid = localUserid
      }
    }
  //默认的用户头像
    let userpic_base = "https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg";
    let localUser = sessionStorage.getItem("user")
    if(localUser){
        this.User = JSON.parse(localUser)
    }else {
      this.axios({
        url: "/user/user/userinfo",
        method: 'get',
        params: {
          'userid': userid,
        }
      }).then((res) => {
        res = res.data;
        if (res.code === 0) {
          let data = res.User;
          if (!data.userpic) {
            data.userpic = userpic_base;
          }
          if (!data.userinfo) {
            data.userinfo = "这个人什么也没说,想必是一代侠客吧!"
          }
          this.User = data;
          //成功后在存到本地,这个是临时存储,避免用户信息修改之后出现数据不同步的情况
          sessionStorage.setItem("user", JSON.stringify(this.User));
        } else {
          this.$message.error(res.msg);
        }
      });
    }
  },
  methods:{
    focusOn(){
      this.focusOnFlag=!this.focusOnFlag;
      if(this.focusOnFlag){
        alert("关注成功")
      }else {
        alert("取关成功")
      }

    },
  },
}
</script>

<style scoped>

.login-background {
  width: 80%;
  margin: 0 auto;
  height: 1200px; /**宽高100%是为了图片铺满屏幕 */
  z-index: 0;
  position: absolute;
}
.show{
  margin: 20px auto;
  width: 100%;

  border: 0px solid #81badc;
  transition: all 0.9s;
  border-radius: 10px;

}
.show:hover{
  box-shadow: 0px 10px 30px rgb(12, 132, 224);
  margin-top: 10px;

}
</style>

博文展示页面

同样的咱们把这个的也拿过来看看。就是这个: 在这里插入图片描述

那么代码也是类似的:

<template>

  <div style="background-color: rgba(239,250,246,0.53)">
    <el-empty
      image="/static/image/empty.gif" :image-size="600" description="暂时木有找到博文~"
      v-if="isEmpty"
    >
    </el-empty>
    <br>
    <br>
    <div style="width: 100%;margin-left: 1%" class="main">
      <el-card shadow="hover" v-for="(message,index) in this.Messages" :key="index">

        <div style="height:100px">
          <div  style="width:12%;height: 100%;border-radius: 100px;display:inline-block;">
            <el-image
              style="width:100%;height: 100%;border-radius: 100px"
              :src="message.blogimg"
              class="image"
              alt="picture"/>
          </div>
          <div style="display:inline-block;margin-left: 5%;width: 80%">
            <p class="message" style="font-weight:bold">

              <router-link class="alink" :to="{ path: '/blogshow',query:{'blogid':message.blogid}}">
                {{message.blogTitle}}
              </router-link>

            </p>
            <p style="font-weight: lighter" class="message"
            >
              {{message.info}}

            </p>
            <p>
              阅读:
              <i class="el-icon-view"></i>
              {{message.viewNumber}}
              &nbsp;&nbsp;
              收藏:
              <i class="el-icon-star-off"></i>
              {{message.collectNumber}}
              &nbsp;&nbsp;
              <i>fork:{{message.forkNumber}}</i>
               &nbsp;
              日期:
              <i>{{message.createTime}}</i>
            </p>
          </div>


        </div>
        <br>
      </el-card>
    </div>
    <br>
    <div class="footer" style="margin: 0 auto;width: 100%;">
      <div class="block" >
        <el-pagination
          background
          layout="total, prev, pager, next, jumper"
          :total=total>
        </el-pagination>
      </div>
    </div>

  </div>

</template>

<script>
export default {
  name: "userblogList",
  data(){
    return{
      isEmpty: true,
      total: 0,
      page: 1,
      limit: 5,
      Messages:[
        {
          userid: null,
          info: null,
          blogid: null,
          contentid: null,
          blogTitle: null,
          userNickname: null,
          userImg: null,
          createTime: null,
          viewNumber: null,
          likeNumber: null,
          collectNumber: null,
          status: null,
          level: null,
          forkNumber: null,
          blogimg: null
        },
      ]
    }
  },
  methods:{
    getDataList(userid){
      //和先前一样,访问服务地址,并且把当前的一些内容进行缓存,减少服务器的压力
      //同时,进入到这里的必须带上userid,如果没有带上那就是非法访问
      let pageSession = sessionStorage.getItem("blogListPageSession");
      let total = sessionStorage.getItem("blogListTotal");
      if(pageSession && total){
        this.Messages = JSON.parse(pageSession);
        this.total = parseInt(total);
        this.isEmpty = (this.total === 0);
      }else {
        this.axios({
          url: "/user/user/userinfo/userarticle",
          method: 'get',
          params: {
            'userid': userid,
            'page': this.page,
            'limit': this.limit
          }
        }).then((res) => {
          res = res.data;
          if (res.code === 0) {
            //这个就是我们的默认展示图片
            let image_base_user = "https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg";
            let image_base_blog = "https://whiteholecloud-dev.oss-cn-shanghai.aliyuncs.com/2022-09-25/8cee84b4-1d03-483f-8376-14d419d84ca5_03.jpg"
            //同样的拿到数据后需要临时保存
            let page = res.page;
            this.total = page.totalCount;
            this.Messages = page.list
            this.isEmpty = (this.total === 0);
            for (let i=0;i<this.Messages.length;i++)
            {
              if(!this.Messages[i].userImg){this.Messages[i].userImg=image_base_user;}
              if(!this.Messages[i].blogimg){this.Messages[i].blogimg=image_base_blog}
            }
            //存储临时缓存
            sessionStorage.setItem("blogListPageSession", JSON.stringify(this.Messages));
            sessionStorage.setItem("blogListTotal",page.totalCount);

          } else {
            this.$message.error(res.msg);
          }
        });
      }
    }
  },
  created() {

    let userid = this.$route.query.userid;
    if(!userid){
      alert("非法访问")
      this.$router.push({path: "/"});
      return;
    }
    //这里的逻辑和进入UserInfo页面的类似
    this.getDataList(userid);

  },
}
</script>

<style scoped>
.message{
  width: 25em;
  overflow: hidden;
  text-overflow:ellipsis;
  white-space: nowrap;
}

.alink{

  text-decoration: none;
  color: #333333;
}

</style>

后端

那么到了咱们的后端技术了 咱们在后端一共有4个服务,其中一个是网关。 在这里插入图片描述

所以咱们今天的亮点就是咱们的后端的一个通讯。 (其实这里面只是简单的CURD真正)由于咱们这个项目没有开发完,所以的话,分布式事务暂时没有去做,还是那句话这玩意不难。当前版本的亮点在于后面的分布式实时消息通讯。当然对我来说最大的挑战还是前端带来的,后端还好。下个版本是推荐算法+AI搜索。整个项目会不断维护更新。可以期待一下,刚好AI和考研需求呈现互补需求,没有太大耽误。

来看的我们的服务: 在这里插入图片描述

我们把所有的Feign都集中起来了: 在这里插入图片描述

数据准备

这里的话就是单纯的远程调用:


/**
 *这里面的所有方法是用来查询用户信息的,所有的求取格式为Get  */
@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Autowired
    UserService userService;
    @Autowired
    HeadimgService headimgService;

    @Autowired
    FeignBlogService feignBlogService;

    @Autowired
    FeignQuizService feignQuizService;

    @Autowired
    FeignAnsService feignAnsService;

    @Autowired
    FeignCommunityService feignCommunityService;

    @Override
    public R userInfo(String userid) {
        UserEntity User = userService.getOne(
                new QueryWrapper<UserEntity>().eq("userid", userid)
        );
        if(User!=null){
            UserInfoEntity userInfoEntity = new UserInfoEntity();
            userInfoEntity.setUserid(User.getUserid());
            userInfoEntity.setUserinfo(User.getInfo());
            userInfoEntity.setNickname(User.getNickname());
            //查询用户的头像信息
            HeadimgEntity headimg = headimgService.getOne(
                    new QueryWrapper<HeadimgEntity>().eq("userid", userid)
                            .orderByDesc("imgid")
                            .last("limit 0,1")

            );
            if(headimg!=null){
                userInfoEntity.setUserpic(headimg.getImgpath());
            }else {
                userInfoEntity.setUserpic(null);
            }
//            可以不写BizCodeEnum.SUCCESSFUL.getMsg(),默认有填写
            return R.ok(BizCodeEnum.SUCCESSFUL.getMsg())
                    .put("User",userInfoEntity);

        }else {
            return R.error(BizCodeEnum.NO_SUCHUSER.getCode(),BizCodeEnum.NO_SUCHUSER.getMsg());
        }
    }

    @Override
    public R userInfoArticle(UserInfoListQueryEntity entity){
        HashMap<String, Object> params = new HashMap<>();
        //组装请求博文列表所需要的数据,当访问的为内部接口时,所有的参数均为Map形式
        params.put("page",entity.getPage());
        params.put("limit",entity.getLimit());
        params.put("accurate","single");
        params.put("table_name","userid");
        params.put("order","desc");
        params.put("status",1);
        params.put("level",1);
        params.put("key",entity.getUserid());
        //开始请求访问
        return feignBlogService.list(params);
    }

    @Override
    public R userInfoQuiz(UserInfoListQueryEntity entity) {
        HashMap<String, Object> params = new HashMap<>();
        //组装请求博文列表所需要的数据,当访问的为内部接口时,所有的参数均为Map形式
        params.put("page",entity.getPage());
        params.put("limit",entity.getLimit());
        params.put("accurate","single");
        params.put("table_name","userid");
        params.put("order","desc");
        params.put("status",1);
        params.put("key",entity.getUserid());
        return feignQuizService.list(params);
    }

    @Override
    public R userInfoAns(UserInfoListQueryEntity entity) {
        HashMap<String, Object> params = new HashMap<>();
        //组装请求博文列表所需要的数据,当访问的为内部接口时,所有的参数均为Map形式
        params.put("page",entity.getPage());
        params.put("limit",entity.getLimit());
        params.put("accurate","single");
        params.put("table_name","userid");
        params.put("order","desc");
        params.put("status",1);
        params.put("key",entity.getUserid());
        return feignAnsService.list(params);
    }

    @Override
    public R userInfoUnity(UserInfoListQueryEntity entity) {
        HashMap<String, Object> params = new HashMap<>();
        //组装请求博文列表所需要的数据,当访问的为内部接口时,所有的参数均为Map形式
        params.put("page",entity.getPage());
        params.put("limit",entity.getLimit());
        params.put("accurate","single");
        params.put("table_name","userid");
        params.put("order","desc");
        params.put("status",1);
        params.put("key",entity.getUserid());
        return feignCommunityService.list(params);
    }
}

这些数据的话都是我们另一个服务需要的数据。例如咱们的博客的服务 在这里插入图片描述

我们这里是分页的,所以在这里面处理:

@Service("blogService")
public class BlogServiceImpl extends ServiceImpl<BlogDao, BlogEntity> implements BlogService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        String key = (String) params.get("key");
        String accurate = (String) params.get("accurate");
        IPage<BlogEntity> page_params = new Query<BlogEntity>().getPage(params);
        QueryWrapper<BlogEntity> blogEntityQueryWrapper = new QueryWrapper<>();
        if(key!=null){
            if(accurate==null){
                //此时表示只有key,没有accurate,说明是后台管理系统在调用
                blogEntityQueryWrapper.like("userid",key).or().
                        like("blogid",key).or().
                        like("user_nickname",key).or().
                        like("blog_title",key);
            }else {
                //此时有accurate说明是用户端在调用
                if(accurate.equals("single")){
                    String table_name = (String) params.get("table_name");
                    String order = (String) params.get("order");
                    Integer status = Integer.valueOf((String) params.get("status"));
                    Integer level = Integer.valueOf((String) params.get("level"));
                    blogEntityQueryWrapper.eq(table_name,key)
                            .eq("status",status)
                            .eq("level",level);
                    if(order.equals("desc")){
                        blogEntityQueryWrapper.orderByDesc("blogid");
                    }

                }else if(accurate.equals("many")){
                    Object accurate_query = params.get("accurate_query");
                    blogEntityQueryWrapper = (QueryWrapper<BlogEntity>) accurate_query;
                }
            }
        }
        IPage<BlogEntity> page = this.page(
                page_params,
                blogEntityQueryWrapper
        );

        return new PageUtils(page);
    }

}

这里的话咱们其实是有两套请求在处理的,预期的话是后台管理的和用户的请求接口是两套方案,前者直接使用security帮忙,后者还是咱们这token机制。

总结

那么今天的博文就水玩了~,那么的话,咱们现在的话有一个分支任务,那么可能接下来是咱们的分支任务要来一下。那么这个任务是啥呢,就是这个智能算法的,期中作业需要解决一个TSP问题,这个时候可能有小伙伴说这不是有脚就行嘛,确实,但是咱们要玩就玩花的,千篇一律多没劲,智能算法我都要吐了。但是咱们还是老规矩,先科普一波,然后咱们用自己的方案。