项目地址:
项目演示地址:http://www.zopthetarget.top
演示账号:admin 密码:123456
一、所使用的环境配置:
编译器:IDEA、VScode
后台框架:SpringBoot
Mybatis-Plus
数据库:Mysql8.0
数据库工具:Navicat premium
前端框架:Vue
Element UI
服务器:腾讯云轻量应用服务器2核4G
项目部署:宝塔面板
二、项目简介
这是一个基于SpringBoot和Vue的客户网页端和后台管理PC端。使用者分为读者,博主,管理员,其中读者和博主都为用户,用户可以登录网站浏览文章,购买图书,查看订单,发布评论,关注,私聊。读者不可以发布文章,博主可以发布文章。管理员用户可以进入后台管理端,可以管理用户,文章,图书,订单,评论,消息。
主要功能:
1.实现用户信息,图书信息,文章信息等的CRUD,以及页面的分页显示。
2.用户权限的分配,不同权限的用户锁能看到的的界面信息和能进行的操作是不同的。
3.实现图片,文件的上传七牛云。
4.跨域配置,MybatisPlus配置。
5.用户的登录注册,拦截器。
6.查询,分类功能。
8.用户评论区,多级评论功能。
7.购物车功能,支付宝沙箱支付。
8.百度云人工智能文字识别。
9.websocket在线聊天室。
10.redis缓存,秒杀高并发使用redis锁。
。。。。
项目展示:(图片)
1.登录界面
2.用户登录后,网页端首页页面
3.页面展示
1)文章详情页面
2)用户可以发表评论
多级评论通过表设计父评论id来区分,在后端获取评论时对数据进行处理,分类。前端再根据对应的内容进行渲染。
@Override
public R getByIdOwn(Integer foreginId) {
List<Comment> list = commentMapper.findAllByForeginId(foreginId);
List<Comment> collect = list.stream().filter(comment -> comment.getPid() == null).collect(Collectors.toList());
for (Comment comment : collect) {
comment.setChildren(list.stream().filter(m -> comment.getId().equals(m.getPid())).collect(Collectors.toList()));
}
return new R(true, collect);
}
package com.example.pet_platform.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.pet_platform.entity.Comment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface CommentMapper extends BaseMapper<Comment> {
List<Comment> findAllByForeginId(@Param("foreginId") Integer foreginId);
List<Comment> findAllByUserId(@Param("UserId") Integer userId);
}
<?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.example.pet_platform.mapper.CommentMapper">
<select id="findAllByForeginId" resultType="com.example.pet_platform.entity.Comment" parameterType="int">
SELECT c.*, pet_user.avatar
FROM (select pet_comment.*
from pet_comment
left join pet_article
on pet_comment.foregin_id = pet_article.aid
where pet_comment.foregin_id = #{foreginId}) c
LEFT JOIN pet_user on c.user_id = pet_user.uid
</select>
<select id="findAllByUserId" resultType="com.example.pet_platform.entity.Comment" parameterType="int">
select pet_comment.*
from pet_comment
left join pet_article
on pet_comment.foregin_id = pet_article.aid
where pet_comment.user_id = #{UserId}
</select>
</mapper>
3)图书列表
4)图书详情+秒杀代金券
秒杀功能的具体代码思路
@Transactional
@Override
public R addVoucher(Integer id, HttpServletRequest request) {
SecKillVoucherDTO secKillVoucherDTO = voucherMapper.selectAllById(id);
if (secKillVoucherDTO.getType().equals(true)){
ZoneId timeZone = ZoneId.systemDefault();
LocalDateTime begin = secKillVoucherDTO.getBegin_time().toInstant().atZone(timeZone).toLocalDateTime();
LocalDateTime end = secKillVoucherDTO.getEnd_time().toInstant().atZone(timeZone).toLocalDateTime();
if (begin.isAfter(LocalDateTime.now())){
return new R(false,"秒杀尚未开始");
}
if (end.isBefore((LocalDateTime.now()))){
return new R(false,"秒杀已经结束了");
}
if(secKillVoucherDTO.getStock()<=0){
return new R(false,"该物品已经抢购完了");
}
}
//秒杀券 先判断是否为秒杀请求-> 秒杀券库存>0->该用户是否购买过该券
if(secKillVoucherDTO.getType().equals(true)&&secKillVoucherDTO.getStock()>0){
String userid = RedisGetUser.getUserid(stringRedisTemplate,request);
secKillVoucherDTO.setUser_id(Integer.parseInt(userid));
SimpleRedisLock lock = new SimpleRedisLock("order:" + userid, stringRedisTemplate);
boolean isLock = lock.tryLock(1200);
if (!isLock){
//获取锁失败
return new R(false,"太火爆了,请稍后再试");
}
try{
OrderService proxy=(OrderService) AopContext.currentProxy();
return proxy.createKill(secKillVoucherDTO);
}finally {
lock.unlock();
}
}
//普通券 且该用户
else if (secKillVoucherDTO.getType().equals(false)){
Date date = new Date();
Order order =new Order();
UserVoucher userVoucher = voucherMapper.getUserVoucher(secKillVoucherDTO);
if (userVoucher==null){
secKillVoucherDTO.setNum(1);
voucherMapper.saveVoucher(secKillVoucherDTO);
//保存订单
order.setTime(DateUtil.formatDateTime(date));
order.setTotal_price(secKillVoucherDTO.getPay_value());
order.setUser_id(secKillVoucherDTO.getUser_id());
order.setNo(DateUtil.format(date, "yyyyMHdd") + System.currentTimeMillis());
order.setState("代金券交易");
order.setName(secKillVoucherDTO.getTitle());
orderMapper.insert(order);
return new R(true,order);
}else {
secKillVoucherDTO.setNum(userVoucher.getNum()+1);
voucherMapper.updatNum(secKillVoucherDTO);
//保存订单
order.setTime(DateUtil.formatDateTime(date));
order.setTotal_price(secKillVoucherDTO.getPay_value());
order.setUser_id(secKillVoucherDTO.getUser_id());
order.setNo(DateUtil.format(date, "yyyyMHdd") + System.currentTimeMillis());
order.setState("代金券交易");
order.setName(secKillVoucherDTO.getTitle());
orderMapper.insert(order);
return new R(true,order);
}
}
return new R(false,"系统繁忙");
}
@Transactional
public R createKill(SecKillVoucherDTO secKillVoucherDTO){
UserVoucher userVoucher = voucherMapper.getUserVoucher(secKillVoucherDTO);
if (userVoucher!=null){
return new R(false,"一人只能买一单");
}
//修改库存
boolean success=seckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id",secKillVoucherDTO.getId()).gt("stock",0).update();
// seckillVoucherMapper.updataKill(secKillVoucherDTO);
if (!success){
return new R(false,"库存不足,扣减失败");
}
secKillVoucherDTO.setNum(1);
//保存到用户代金券
voucherMapper.saveVoucher(secKillVoucherDTO);
//保存订单
Date date = new Date();
Order order =new Order();
order.setTime(DateUtil.formatDateTime(date));
order.setTotal_price(secKillVoucherDTO.getPay_value());
order.setNo(DateUtil.format(date, "yyyyMHdd") + System.currentTimeMillis());
order.setState("代金券交易");
order.setUser_id(secKillVoucherDTO.getUser_id());
order.setName(secKillVoucherDTO.getTitle());
orderMapper.insert(order);
return new R(true,order);
}
package com.example.pet_platform.util;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
public class SimpleRedisLock implements ILock{
private String name;
private StringRedisTemplate stringRedisTemplate;
public SimpleRedisLock(String name,StringRedisTemplate stringRedisTemplate){
this.name=name;
this.stringRedisTemplate=stringRedisTemplate;
}
private static final String KEY_PREFIX="lock:";
@Override
public boolean tryLock(long timeoutSec) {
//获取线程标示
long threadId = Thread.currentThread().getId();
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId + "", timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
@Override
public void unlock() {
stringRedisTemplate.delete(KEY_PREFIX+name);
}
}
redis锁的思路,先获取该线程的id,再通过redis的stringRedisTemplate.opsForValue().setIfAbsent方法来对redis进行写入,其中key为lock:线程id,该方法的效果为,当要写入的key在数据库中已经存在会返回false,不存在返回true。再该线程完成任务后将该key删除。这样可以确保只有一个线程再进行数据的操作。
5)我的订单
6)个人中心
7)发表文章
图片的上传使用element-ui的upload组件,上传到七牛云,返回在线地址到请求参数中,保存按钮将其存入数据库。
8)购物车
前端组件增加和减少都做了限制,使用多选组件进行判断是否选中,点击付款跳转到支付宝沙箱API,支持回调。ps:如果已经部署线上服务器只需要更改支付宝沙箱的回调地址为自己的服务器地址。如果是本地的需要开启内网穿透。
参考:https://blog.youkuaiyun.com/xqnode/article/details/124457790
9)在线聊天
package com.example.pet_platform.component;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.pet_platform.controller.util.CensorResult;
import com.example.pet_platform.entity.Message;
import com.example.pet_platform.entity.User;
import com.example.pet_platform.service.BaiduContentCensorService;
import com.example.pet_platform.service.MessageService;
import com.example.pet_platform.service.UserService;
import com.example.pet_platform.util.SpringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author websocket服务
*/
@ServerEndpoint(value = "/imserver/{username}")
@Component
public class WebSocketServer {
private static ApplicationContext applicationContext;
public static void setApplicationContext(ApplicationContext context) {
applicationContext = context;
}
//饿汉加载
private static BaiduContentCensorService baiduContentCensorService;
@Autowired
public void setBaiduContentCensorService(BaiduContentCensorService baiduContentCensorService) {
WebSocketServer.baiduContentCensorService = baiduContentCensorService;
}
private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
/**
* 记录当前在线连接数
*/
public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
sessionMap.put(username, session);
log.info("有新用户加入,username={}, 当前在线人数为:{}", username, sessionMap.size());
JSONObject result = new JSONObject();
JSONArray array = new JSONArray();
result.set("users", array);
for (Object key : sessionMap.keySet()) {
JSONObject jsonObject = new JSONObject();
jsonObject.set("username", key);
array.add(jsonObject);
}
sendAllMessage(JSONUtil.toJsonStr(result)); // 后台发送消息给所有的客户端
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam("username") String username) {
sessionMap.remove(username);
log.info("有一连接关闭,移除username={}的用户session, 当前在线人数为:{}", username, sessionMap.size());
}
/**
* 收到客户端消息后调用的方法
* 后台收到客户端发送过来的消息
* onMessage 是一个消息的中转站
* 接受 浏览器端 socket.send 发送过来的 json数据
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session, @PathParam("username") String username) {
log.info("服务端收到用户username={}的消息:{}", username, message);
JSONObject obj = JSONUtil.parseObj(message);
String toUsername = obj.getStr("to"); // to表示发送给哪个用户,比如 admin
String text = obj.getStr("text"); // 发送的消息文本 hello
Message message1 = new Message();
message1.setUsername(username);
message1.setToUsername(toUsername);
CensorResult result = baiduContentCensorService.getCommonTextCensorResult(text);
Map map = com.alibaba.fastjson.JSONObject.parseObject(com.alibaba.fastjson.JSONObject.toJSONString(com.alibaba.fastjson.JSONObject.parseObject(result.getTextCensorJson())), Map.class);
System.out.println(map.get("data"));
if (map.get("data") != null) {
int length = text.length();
StringBuilder x = new StringBuilder();
for (int i = 0; i < length; i++) {
x.append("*");
}
System.err.println();
text = x.toString();
message1.setMessage(x.toString());
} else {
message1.setMessage(text);
}
UserService bean = applicationContext.getBean(UserService.class);
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("username", username);
User one = bean.getOne(qw);
message1.setFrom_uid(one.getUid());
QueryWrapper<User> qw1 = new QueryWrapper<>();
qw1.eq("username", toUsername);
User one1 = bean.getOne(qw1);
message1.setTo_uid(one1.getUid());
MessageService bean1 = applicationContext.getBean(MessageService.class);
bean1.save(message1);
// {"to": "admin", "text": "聊天文本"}
Session toSession = sessionMap.get(toUsername); // 根据 to用户名来获取 session,再通过session发送消息文本
JSONObject jsonObject = new JSONObject();
jsonObject.set("from", username); // from 是 zhang
jsonObject.set("text", text); // text 同上面的text
if (toSession != null) {
this.sendMessage(jsonObject.toString(), toSession);
log.info("发送给用户username={},消息:{}", toUsername, jsonObject.toString());
} else {
jsonObject.set("text", false);
this.sendMessage(jsonObject.toString(), session);
log.info("发送失败,未找到用户username={}的session", toUsername);
}
}
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 服务端发送消息给客户端
*/
private void sendMessage(String message, Session toSession) {
try {
log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
toSession.getBasicRemote().sendText(message);
} catch (Exception e) {
log.error("服务端发送消息给客户端失败", e);
}
}
/**
* 服务端发送消息给所有客户端
*/
private void sendAllMessage(String message) {
try {
for (Session session : sessionMap.values()) {
log.info("服务端给客户端[{}]发送消息{}", session.getId(), message);
Map<String, String> map = new HashMap<>();
map.put("sessionId", session.getId());
map.put("message", message);
session.getBasicRemote().sendText(message);
}
} catch (Exception e) {
log.error("服务端发送消息给客户端失败", e);
}
}
}
10)后台首页
11)图书管理
12)用户管理
13)订单管理
14)管理员在线联系用户