持续创作,加速成长!这是我参与「掘金日新计划 · 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}}
收藏:
<i class="el-icon-star-off"></i>
{{message.collectNumber}}
<i>fork:{{message.forkNumber}}</i>
日期:
<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问题,这个时候可能有小伙伴说这不是有脚就行嘛,确实,但是咱们要玩就玩花的,千篇一律多没劲,智能算法我都要吐了。但是咱们还是老规矩,先科普一波,然后咱们用自己的方案。