知识点7--SSM项目首页功能优化

继CMS-Demo的1.0版本完成之后,我们要进入完善阶段,必进只是完成了基本功能,不影响运行而已。本篇开始将1.0优化成为2.0版本,同时在优化中我们重点是要加入ES、Redis等数据技术支持。

首先对于一个CMS来说它的首页一定是重点被查询页面,所以我们优化的第一个点就是首页的响应速度,我们现在可以看一下首页查询用时。
在这里插入图片描述
我们可以看到,现在后台查询分页每页6篇,也不多,但是却用了1.5秒,这个速度,我们是无法忍受的,现在而且现在还是本地运行,我们直接开发时访问,假设http://localhost:8080/index.do这个路径正式使用上线了,大量的查询在加上网络的延迟,那我们就一首凉凉送给自己了,我们先优化第一次,使用多线程执行这个任务。

/**
 *
 * @Title: index
 * @Description: 进入首页
 * @return
 * @return: String
 */
@RequestMapping("index.do")
public String index(Model model,Article article,@RequestParam(defaultValue="1")Integer pageNum) {
	//封装查询条件
	model.addAttribute("article", article);

	//使用线程
	Thread t1;
	Thread t2;
	Thread t3;
	Thread t4;
	//查询所有的栏目,该线程为必须品
	t1=new Thread(new Runnable() {
		@Override
		public void run() {
			List<Channel> channels = channelService.selects();
			model.addAttribute("channels", channels);
		}
	});

	// 判断栏目ID 不为空 也就是说当前不是查询热点那么就要查询其下分类
	t2=new Thread(new Runnable() {

		@Override
		public void run() {

			if(article.getChannelId()!=null){
				List<Category> categorys = channelService.selectsByChannelId(article.getChannelId());
				model.addAttribute("categorys", categorys);
			}else{
				//如果栏目id是空的那么就代表这查询的是热点,并为为热点查询广告
				List<Slide> slides = slideService.getAll();
				model.addAttribute("slides", slides);
				//限制查询热点文章
				article.setHot(1);
			}
		}
	});

	//前两个线程决定查什么文章,第三个线程正式查文章
	t3=new Thread(new Runnable() {

		@Override
		public void run() {
			try {
				t2.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//无论是什么情况控制查询文章不是被逻辑删除的
			article.setDeleted(0);
			//不能查询非审核之后的文章
			article.setStatus(1);
			//查询符合条件的所有文章
			List<Article> selectArticle = articleService.selectArticle(article, pageNum, 6);
			PageInfo info=new PageInfo<>(selectArticle);
			model.addAttribute("info", info);
		}

	});

	//为首页查询五条最新的文章
	t4=new Thread(new Runnable() {

		@Override
		public void run() {

			//	封装该查询条件
			Article latest = new Article();
			latest.setDeleted(0);
			//不能查询非审核之后的文章
			latest.setStatus(1);
			List<Article> newArticles = articleService.selectArticle(latest, 1, 5);
			PageInfo lastArticles=new PageInfo<>(newArticles);
			model.addAttribute("lastArticles", lastArticles);
		}
	});

	//启动线程并保证线程顺序
	t1.start();
	t2.start();
	t3.start();
	t4.start();
	try {
		t1.join();
		t3.join();
		t4.join();
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	
	return "index/index";
}

我们将首页的Controller使用线程分为四个任务一起跑,现在再执行项目看一下任务需要多少秒。
在这里插入图片描述
现在我们可以看到,总耗时不到25毫秒,没有优化前,单一个相应就1.5秒,暂时首页的展示请求响应速度比较满意,先暂时这样。


第二点优化是首页点击详情的时候,只展示一个详情,太单调了,我们在详情页面中搞一个收藏和评论,但是在开发之前,我们要想一个问题,针对与用户的行为,如果他没有登录我们可以做吗?所以我们需要先开发一个登录拦截器

package com.wy.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
 * 
 * @ClassName: MyInterceptor 
 * @Description: 个人中心拦截器
 * @author: charles
 * @date: 2020年4月10日 上午9:56:23
 */
public class MyInterceptor extends HandlerInterceptorAdapter {
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		
	     //不拦截规则--- 如果用户已经登录则不拦截 false:如果有session 则返回session.如果没有则返回null
		HttpSession session = request.getSession(false);
		if(session!=null) {//如果存在session 
			Object user = session.getAttribute("user");//则从session获取登录的user对象
			if(user!=null)//如果对象不为空
			 return true;//放行
		}
		//没有登录,跳转到登录页面
		request.setAttribute("msg", "请登录后再试");
		request.getRequestDispatcher("/WEB-INF/view/passport/login.jsp").forward(request, response);
		
		return false;
	}

}

但是一个就够了吗?如果后续大家对项目有自己的扩展,总不可能在一个拦截器里写一堆if来判断登录角色吧?所以我们还需要一个管理员的拦截器

package com.wy.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
 * 
 * @ClassName: MyInterceptor 
 * @Description: 管理员拦截器
 * @author: charles
 * @date: 2020年4月10日 上午9:56:23
 */
public class AdminInterceptor extends HandlerInterceptorAdapter {
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		
	     //不拦截规则--- 如果用户已经登录则不拦截 false:如果有session 则返回session.如果没有则返回null
		HttpSession session = request.getSession(false);
		if(session!=null) {//如果存在session 
			Object user = session.getAttribute("admin");//则从session获取登录的user对象
			if(user!=null)//如果对象不为空
			 return true;//放行
		}
		//没有登录,跳转到登录页面
		request.setAttribute("msg", "请登录后再试");
		request.getRequestDispatcher("/WEB-INF/view/passport/login_admin.jsp").forward(request, response);
		return false;
	}

}

随后在springmvc的配置文件中注册这两拦截器

<!-- 配置拦截器 -->
<mvc:interceptors>
    <!-- 1个人中心拦截器 -->
    <mvc:interceptor>
        <!-- 拦截规则 -->
        <mvc:mapping path="/my/**" />
        <!-- 不拦截规则 -->
        <mvc:exclude-mapping path="/resource/**" />
        <!-- 拦截器处理类 -->
        <bean class="com.wy.interceptor.MyInterceptor" />
    </mvc:interceptor>

    <!-- 2管理员中心拦截器 -->
    <mvc:interceptor>
        <!-- 拦截规则 -->
        <mvc:mapping path="/admin/**" />
        <!-- 不拦截规则 -->
        <mvc:exclude-mapping path="/resource/**" />
        <!-- 拦截器处理类 -->
        <bean class="com.wy.interceptor.AdminInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

现在我们可以运行一下项目,会发现个人中心、管理员两个模块均需要登录,不过这里存在一个功能盲点,就是这个方式跳转的登录由于不是load方式因此长度参照物成了整个浏览器,导致文本框被拉的很长,大家可以自己扩展一下,提供两种思路,一是开发一个新的用于登录的页面,二是使用文档函数把配合js判断当前页面的状态并控制长度,效果可以参考github官网登录那样
在这里插入图片描述


现在我们就可以做下一步了,首先在首页的详情页面添加收藏用的按钮以及评论的展示

<!-- 收藏文章 -->
<div align="right">
	<c:if test="${isCollect==0 || isCollect==null}">
		<a href="javascript:collect()">☆ 收藏</a>
	</c:if>
	<c:if test="${isCollect==1}">
		<span class="text-danger">★ 已收藏</span>
	</c:if>
</div>
<!-- 增加评论 -->
<c:if test="${null!=sessionScope.user}">
	<div>
		输入评论:
		<textarea rows="8" cols="110" name="content"></textarea>
		<br>
		<button class="btn btn-info" onclick="addComment()">提交评论</button>
	</div>
</c:if>
<!-- 显示评论 -->
<div>
	<c:forEach items="${info.list}" var="comment">
   ${comment.user.username }  <fmt:formatDate
			value="${comment.created}" pattern="yyyy-MM-dd HH:mm:ss" />
		<br>
		<p class="mt-3">${comment.content }</p>
		<hr>
	</c:forEach>
</div>

此时我们就需要先该改一下,详情页跳转的Controller,我们要查询出当前文章是否被当前用户收藏,以及当前文章的评论,因此我们要准备写评论、收藏Bean的Dao和Service

首先开发数据库中两张表的实体Bean,没有数据库环境的见知识点1,第一个是收藏Bean

package com.wy.bean;

import java.io.Serializable;
import java.util.Date;

/**
 * 
 * @ClassName: Collect 
 * @Description: 文章的收藏
 * @author: charles
 * @date: 2020年2月15日 上午8:41:22
 */
public class Collect  implements Serializable{
	
	/**
	 * @fieldName: serialVersionUID
	 * @fieldType: long
	 * @Description: TODO
	 */
	private static final long serialVersionUID = 1L;
	private Integer id;
	private String text;//文章的标题
	private String url;//文章的url
	private Integer userId;//收藏人ID
	private  User user;//收藏人
	private Date created;//收藏时间
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	
	public Integer getUserId() {
		return userId;
	}
	public void setUserId(Integer userId) {
		this.userId = userId;
	}
	public String getText() {
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public User getUser() {
		return user;
	}
	public void setUser(User user) {
		this.user = user;
	}
	public Date getCreated() {
		return created;
	}
	public void setCreated(Date created) {
		this.created = created;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((userId == null) ? 0 : userId.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Collect other = (Collect) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		if (userId == null) {
			if (other.userId != null)
				return false;
		} else if (!userId.equals(other.userId))
			return false;
		return true;
	}
	
}

随后是评论Bean

package com.wy.bean;

import java.io.Serializable;
import java.util.Date;
/**
 * 
 * @ClassName: Comment 
 * @Description: 评论表
 * @author: charles
 * @date: 2020年3月13日 上午11:32:22
 */
public class Comment implements Serializable {

	/**
	 * @fieldName: serialVersionUID
	 * @fieldType: long
	 * @Description: TODO
	 */
	private static final long serialVersionUID = 1L;
	
	private Integer id;
	private Integer userId;//用户id
	private Integer articleId;//文章id
	private String content;//评论
	private Date created;//时间
	
	private  User user;
	private Article article;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public Integer getUserId() {
		return userId;
	}
	public void setUserId(Integer userId) {
		this.userId = userId;
	}
	public Integer getArticleId() {
		return articleId;
	}
	public void setArticleId(Integer articleId) {
		this.articleId = articleId;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	
	public Date getCreated() {
		return created;
	}
	public void setCreated(Date created) {
		this.created = created;
	}
	public User getUser() {
		return user;
	}
	public void setUser(User user) {
		this.user = user;
	}
	public Article getArticle() {
		return article;
	}
	public void setArticle(Article article) {
		this.article = article;
	}
}

有了Bean,我们就要开发对应的Dao和Service层,同样第一个是收藏Dao

package com.wy.dao;

import java.util.List;


import org.apache.ibatis.annotations.Param;

import com.wy.bean.Collect;


/**
 * 
 * @ClassName: CollectMapper 
 * @Description: 文章收藏
 * @author: charles
 * @date: 2020年4月11日 上午9:03:51
 */
public interface CollectMapper {
	/**
	 * 
	 * @Title: insert 
	 * @Description: 增加
	 * @param collect
	 * @return
	 * @return: int
	 */
	int insert(Collect collect);
	/**
	 * 
	 * @Title: selects 
	 * @Description: 查询
	 * @param collect
	 * @return
	 * @return: List<Collect>
	 */
	List<Collect> selects(Collect collect);
	/**
	 * 
	 * @Title: selectCount 
	 * @Description: 查询注册用户是否收藏text的文章
	 * @param text
	 * @param userId
	 * @return
	 * @return: int  1:已收藏    0:未收藏
	 */
	int selectCount(@Param("text") String text,@Param("userId")Integer userId);
	/**
	 * 
	 * @Title: deleteCollect 
	 * @Description: 根据ID删除收藏
	 * @param id
	 * @return
	 * @return: int
	 */
	int deleteCollect(Integer id);

}

以及同步的xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.wy.dao.CollectMapper">

	<select id="selectCount" resultType="int">
		select count(id) from cms_collect where user_id=#{userId} and text =#{text}
	</select>
   
	<insert id="insert">
		insert into cms_collect (text,user_id,url,created)
		values(#{text},#{userId},#{url},#{created})
	</insert>
	
	
	<select id="selects" resultType="com.wy.bean.Collect">
		select * from cms_collect
		<where>
			<if test="userId!=null">
				user_id =#{userId}
			</if>
		</where>
      order by created desc
	</select>
	
	<delete id="deleteCollect">
		delete from cms_collect where id =#{id}
	</delete>

</mapper>

第二个是评论的Dao

package com.wy.dao;

import java.util.List;

import com.wy.bean.Comment;


/**
 * 
 * @ClassName: CommentMapper 
 * @Description: 评论
 * @author: charles
 * @date: 2020年4月11日 上午11:25:04
 */
public interface CommentMapper {
	/**
	 * 
	 * @Title: insert 
	 * @Description: 增加评论
	 * @param comment
	 * @return
	 * @return: int
	 */
	int insert(Comment comment);
	/**
	 * 
	 * @Title: selects 
	 * @Description: 根据文章id 查询评论
	 * @param articleId
	 * @return
	 * @return: List<Comment>
	 */
	List<Comment> selects(Integer articleId);
	
	/**
	 * 
	 * @Title: updateAritlce 
	 * @Description: 增加文章的评论数量
	 * @param id
	 * @return
	 * @return: int
	 */
	int updateAritlce(Integer id);

}

以及同步的xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.wy.dao.CommentMapper">
	<insert id="insert">
		insert into
		cms_comment(user_id,article_id,content,created)
		values(#{userId},#{articleId},#{content},#{created})
	</insert>

	<resultMap type="com.wy.bean.Comment" id="commentResultMap">
		<id column="id" property="id" />
		<result column="user_id" property="userId" />
		<result column="article_id" property="articleId" />
		<result column="content" property="content" />
		<result column="created" property="created" />
		<!-- 评论人 -->
		<association property="user" javaType="com.wy.bean.User"
			select="selectById" column="user_id"></association>

	</resultMap>


	<select id="selects" resultMap="commentResultMap">
		select * from cms_comment where article_id=#{articleId}
	  order by created desc

	</select>

	<select id="selectById" resultType="com.wy.bean.User">
		select * from cms_user where id=#{id}
	</select>
	
	<!-- 让文章的评论数+1 -->
	<update id="updateAritlce">
	 update cms_article set comment_num =comment_num +1  where id =#{id}
	</update>

</mapper>

然后就是收藏的Service

package com.wy.service;

import org.apache.ibatis.annotations.Param;

import com.github.pagehelper.PageInfo;
import com.wy.bean.Collect;

public interface CollectService {

	
	/**
	 * 
	 * @Title: deleteCollect 
	 * @Description: 根据ID删除收藏
	 * @param id
	 * @return
	 * @return: int
	 */
	int deleteCollect(Integer id);
	/**
	 * 
	 * @Title: insert 
	 * @Description: 增加
	 * @param collect
	 * @return
	 * @return: int
	 */
	boolean insert(Collect collect);
	/**
	 * 
	 * @Title: selects 
	 * @Description: 查询
	 * @param collect
	 * @return
	 * @return: List<Collect>
	 */
	PageInfo<Collect> selects(Collect collect,Integer page,Integer pageSize);
	
	
	/**
	 * 
	 * @Title: selectCount 
	 * @Description: 查询注册用户是否收藏text的文章
	 * @param text
	 * @param userId
	 * @return
	 * @return: int  1:已收藏    0:未收藏
	 */
	int selectCount(@Param("text") String text,@Param("userId")Integer userId);
}

以及对应的实现类

package com.wy.service;

import java.util.List;


import javax.annotation.Resource;

import com.wy.utils.StringUtil;
import org.springframework.stereotype.Service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.wy.bean.Collect;
import com.wy.dao.CollectMapper;
import com.wy.utils.CMSException;

@Service
public class CollectServiceImpl implements CollectService {
	@Resource
	private CollectMapper collectMapper;

	@Override
	public boolean insert(Collect collect) {
		
		if(!StringUtil.isHttpUrl(collect.getUrl()))
			throw new CMSException("url 不合法");
		
		return collectMapper.insert(collect) >0;
	}

	@Override
	public PageInfo<Collect> selects(Collect collect, Integer page, Integer pageSize) {
		PageHelper.startPage(page, pageSize);
		List<Collect> selects = collectMapper.selects(collect);
		return new PageInfo<Collect>(selects);
	}

	@Override
	public int selectCount(String text, Integer userId) {
		// TODO Auto-generated method stub
		return collectMapper.selectCount(text, userId);
	}

	@Override
	public int deleteCollect(Integer id) {
		// TODO Auto-generated method stub
		return collectMapper.deleteCollect(id);
	}

}

最后是评论的Service

package com.wy.service;

import java.util.List;

import com.github.pagehelper.PageInfo;
import com.wy.bean.Comment;

public interface CommentService {

	
	/**
	 * 
	 * @Title: insert 
	 * @Description: 增加评论
	 * @param comment
	 * @return
	 * @return: int
	 */
	int insert(Comment comment);
	/**
	 * 
	 * @Title: selects 
	 * @Description: 根据文章id 查询评论
	 * @param articleId
	 * @return
	 * @return: List<Comment>
	 */
	PageInfo<Comment> selects(Integer articleId,Integer page,Integer pageSize);
}

以及实现类

package com.wy.service;

import java.util.List;


import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.wy.bean.Comment;
import com.wy.dao.CommentMapper;
@Service
public class CommentServiceImpl  implements CommentService{
	@Resource
	private CommentMapper commentMapper;

	@Override
	public int insert(Comment comment) {
		try {
			commentMapper.insert(comment);//增加评论
			commentMapper.updateAritlce(comment.getArticleId());//让评论数量增加1
			return 1;
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("评论失败");
		}
		
	}

	@Override
	public PageInfo<Comment> selects(Integer articleId, Integer page, Integer pageSize) {
		PageHelper.startPage(page, pageSize);
		List<Comment> list = commentMapper.selects(articleId);
		return new PageInfo<Comment>(list);
	}

}

有了Dao和Service,我们就需要在首页的Controller中注入他们

@Resource
private CollectService collectService;//收藏

@Resource
private CommentService commentService;//评论

注入后我们就可以更改首页查询详情的Controller了,在原先的基础上,查询出是否收藏文章以及文章评论

/**
	 *
	 * @Title: detail
	 * @Description: 文章详情
	 * @param id
	 * @return
	 * @return: String
	 */
	@RequestMapping("detail.do")
	public String detail(Model model, Integer id, HttpSession session, @RequestParam(defaultValue="1")Integer page) {
		//查询文章
		Article article = articleService.select(id);
		model.addAttribute("article", article);

		//查询文章是否被当前用户收藏
		// 前提:如果用户已经登录则查询是否收藏
		User user=(User) session.getAttribute("user");
		if (null != user) {
			int isCollect = collectService.selectCount(article.getTitle(), user.getId());
			model.addAttribute("isCollect", isCollect);
		}

		//查询评论
		PageInfo<Comment> info = commentService.selects(id, page, 5);
		model.addAttribute("info", info);

		return "index/article";
	}

现在我们既可运行项目看一下首页详情的效果。注意详情页没有绑定事件呢,正常应该没有动态效果,就是说页面有东西,但没有实际效果。
在这里插入图片描述


现在我们在个人中心Controller中准备收藏和评论的请求接收

@Resource
private CollectService collectService;//收藏

@Resource
private CommentService commentService;//评论

/**
 *
 * @Title: collect
 * @Description: 收藏文章
 * @return
 * @return: boolean
 */
@ResponseBody
@RequestMapping("collect.do")
public boolean collect(Collect collect, HttpSession session) {
	User user = (User) session.getAttribute("user");
	if (user != null) {// 如果已经登录则执行收藏
		collect.setUserId(user.getId());// 收藏人
		collect.setCreated(new Date());// 收藏时间
		return collectService.insert(collect);
	}
	return false;// 没有登录则不执行收藏
}

/**
 *
 * @Title: collect
 * @Description: 评论文章
 * @return
 * @return: boolean
 */
@ResponseBody
@RequestMapping("addComment.do")
public boolean addComment(Comment comment, HttpSession session) {
	User user = (User) session.getAttribute("user");
	if (user != null) {// 如果已经登录则才能评论
		comment.setUserId(user.getId());// 评论人
		comment.setCreated(new Date());// 收藏时间
		return commentService.insert(comment) >0;
	}
	return false;// 没有登录则不能评论

}

最后还要给首页详情页绑定JS事件

<script type="text/javascript">
	//收藏
	function collect() {
		var text = '${article.title}';//获取文章标题
		var url = window.location.href;//获取文章的url

		$.post("/my/collect.do", {
			text : text,
			url : url
		}, function(flag) {
			if (flag) {
				alert("收藏成功");
				window.location.reload();
			} else {
				alert("收藏失败,请登录后再试")
			}
		})
	}
	//增加评论
	function addComment() {
		var content = $("[name='content']").val();
		var articleId = '${article.id}';
		$.post("/my/addComment.do", {
			content : content,
			articleId : articleId
		}, function(flag) {
			if (flag) {
				alert("评论成功");
				window.location.reload();
			} else {
				alert("评论失败。请登录后重试")
			}
		})
	}
</script>

最后运行出效果
在这里插入图片描述
没有登录是不展示评论的

但是此时当你点击收藏时会有一个bug,就是在没有登录的情况下会显示收藏成功,但是再次刷新页面的时候页面任然正常,这是因为收藏和评论的Controller被拦截器拦截了,可这两个本身有有着是否有Session的判断,后端的数据就是安全的,但前端收不到返回结果,导致程序混乱直接运行了为真的代码,把两个Controller的拦截器释放就行正常了
在这里插入图片描述
回复正常后,我们测试登录后执行收藏、评论
在这里插入图片描述
在这里插入图片描述
到此首页优化完成,至于个人首页中的我的收藏、我的评论留给大家扩展,因为这两个功能核心点太简单了就是查对应的列表展示,最多再多做一个删除评论和取消收藏,所以这两个小功能大家自己做吧,再往后的知识点就该到ES那些重点组件了,本项目目前以上传github :https://github.com/wangyang159/cmsdemo

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值