项目介绍
该博客是基于ssm实现的个人博客系统,适合初学ssm和制作个人博客的通过学习。项目分为用户模块、文章模块、分类模块、评论模块、链接模块、菜单模块、公告模块、页面模块、标签模块、网站信息模块。
其权限分为普通用户和管理员用户,普通用户其主要功能包括用户注册、用户登录、修改密码、忘记密码、修改个人信息、游览文章、查找文章、删除个人文章、编辑个人文章、发表文章、查看评论,管理员等。管理员用户拥有普通用户的所有功能,额外还有管理用户、管理分类、管理链接、管理菜单、管理公告、管理页面、管理标签、管理网站信息等功能。
项目非完全分离式项目,前端大体使用JSP+LayUi+javascript架构,数据交互使用jsp模板渲染和json。后端使用MVC架构设计模式(SSM框架),将整个系统划分为View层,Controller层,Service层,DAO层四层,使用Spring MVC负责请求的转发和视图管理,Spring实现业务对象管理,Mybatis作为数据对象的持久化引擎。
其涉及技术包括:
Spring
+springMVC
+Mybatis
+MySQL8.x
+JWT
+Redis
+Mybatis-Plus
+pagehelper
+JSP
+rapid
+JavaScript
+hutool
+LayUi
+json
+cookie
+Nginx
+Tomcat
项目数据库
er图
概念模型图
物理模型图
项目环境
搭建ssm环境
如何搭建ssm环境我就不介绍了
集成redis
-
导入依赖
<!-- redis--> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.1.8.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
-
redis.properties
#redis的ip redis.host=120.79.188.192 #端口号 redis.port=6379 #密码 redis.pass=******** #连接池空闲最大连接数(使用负值表示没有限制) redis.maxIdle=20 #连接池空闲最小连接数 redis.minIdle=0 #连接池最大连接数(使用负值表示没有限制) redis.maxActive=100 #连接池最大阻塞时间(使用负值表示没有限制) redis.maxWait=-1 #使用连接时,检测连接是否成功 redis.testOnBorrow=true #设置库号,数据库索引 redis.database=1 #设置连接超时时间 redis.timeout=3000
-
redis-context.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <!-- scanner redis properties --> <context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true"/> <!--(1)如果你有多个数据源需要通过<context:property-placeholder管理,且不愿意放在一个配置文件里,那么一定要加上ignore-unresolvable=“true"--> <!--(2)注意新版的(具体从哪个版本开始不清楚,有兴趣可以查一下)JedisPoolConfig的property name,不是maxActive而是maxTotal,而且没有maxWait属性,建议看一下Jedis源码。--> <!-- redis连接池 --> <bean id="jedisConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="${redis.maxActive}"></property> <property name="maxIdle" value="${redis.maxIdle}"></property> <property name="minIdle" value="${redis.minIdle}"></property> <property name="maxWaitMillis" value="${redis.maxWait}"></property> <property name="testOnBorrow" value="${redis.testOnBorrow}"></property> </bean> <!-- redis连接工厂 --> <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="${redis.host}"></property> <property name="port" value="${redis.port}"></property> <property name="password" value="${redis.pass}"></property> <property name="database" value="${redis.database}"/> <property name="poolConfig" ref="jedisConfig"></property> <property name="timeout" value="${redis.timeout}"/> </bean> <!-- redis操作模板,这里采用尽量面向对象的模板 --> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="connectionFactory"/> <!-- 如果不配置Serializer,那么存储的时候只能使用String,如果用对象类型存储,那么会提示错误 can't cast to String!!!--> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <!-- <property name="valueSerializer">--> <!-- <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>--> <!-- </property>--> <!-- <property name="hashValueSerializer">--> <!-- <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>--> <!-- </property>--> <!--开启事务--> <property name="enableTransactionSupport" value="true"/> </bean> </beans>
-
导入redis工具类(内容太多,需要的可以在我博客搜索)
集成JWT
-
导入依赖
<!-- java-jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>
-
使用工具类
import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import java.util.Calendar; import java.util.Map; /** * project : blog-ssm * <p> * * </p> * * @autor:lzj * @date:2022/6/1 */ public class JWTUtils { private static final String SIGN="!qwe@[wef";//后面从数据库中获取 /** * 生成token header.payload.sign * 由于header有默认值,所以我们可以不需要设置,如果有需要可以进行设置 */ public static String getToken(Map<String,String> map){ Calendar instance = Calendar.getInstance(); instance.add(Calendar.DATE,7);//默认7天过期 //创建jwt builder JWTCreator.Builder builder = JWT.create(); //playload map.forEach((k,v)->{builder.withClaim(k,v);}); String token = builder.withExpiresAt(instance.getTime())//令牌过期时间 .sign(Algorithm.HMAC256(SIGN));//签名 return token; } /** * 验证token合法性,并返回DecodedJWT对象 */ public static DecodedJWT verify(String token){ return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token); } /** * 获取token信息方法 */ public static DecodedJWT getTokenInfo(String token){ DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token); return verify; } }
-
配置拦截器类
import com.auth0.jwt.exceptions.AlgorithmMismatchException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.fasterxml.jackson.databind.ObjectMapper; import com.lzj.utils.CookieUtil; import com.lzj.utils.JWTUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; /** * project : blog-ssm * <p> * * </p> * * @autor:lzj * @date:2022/6/1 */ @Component public class JWTInterceptor implements HandlerInterceptor { @Autowired private RedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("进入了JWT拦截器"); //如果请求为 OPTIONS 请求,则返回 true,否则需要通过jwt验证 if (HttpMethod.OPTIONS.toString().equals(request.getMethod())){ System.out.println("OPTIONS请求,放行"); return true; } Cookie uid = CookieUtil.getCookieByName(request, "uid"); if(uid==null){ response.sendRedirect("/login"); return false; } HashMap<String, Object> map = new HashMap<>(); //获取请求头中的令牌 String cookieUid = uid.getValue(); String token = (String) redisTemplate.opsForValue().get(cookieUid); // String token = request.getHeader("Authorization");//所以前端需要将token放到请求头的Authorization中 // System.out.println("请求头中的token:"+token); try { JWTUtils.verify(token);//验证令牌 return true; }catch (SignatureVerificationException e){ e.printStackTrace(); map.put("msg","无效签名"); }catch (TokenExpiredException e){ e.printStackTrace(); map.put("msg","token过期"); }catch (AlgorithmMismatchException e){ e.printStackTrace(); map.put("msg","token算法不一致"); }catch (Exception e){ e.printStackTrace(); map.put("msg","token无效"); } map.put("state",false);//设置状态 //将map转为json 使用jackson String json = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().print(json); //删除cookie中的uid CookieUtil.removeCookie(response,"uid"); response.sendRedirect("/login"); return false; } }
-
将拦截器配置到spring容器中
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/register"/> <mvc:exclude-mapping path="/registerSubmit"/> <mvc:exclude-mapping path="/loginVerify"/> <mvc:exclude-mapping path="/css/**"/> <mvc:exclude-mapping path="/img/**"/> <mvc:exclude-mapping path="/js/**"/> <mvc:exclude-mapping path="/plugin/**"/> <mvc:exclude-mapping path="/Error/**"/> <mvc:exclude-mapping path="/article/**"/> <mvc:exclude-mapping path="/category/**"/> <mvc:exclude-mapping path="/tag/**"/> <mvc:exclude-mapping path="/user/**"/> <mvc:exclude-mapping path="/search"/> <bean class="com.lzj.interceptors.JWTInterceptor"/> </mvc:interceptor> </mvc:interceptors>
集成OSS对象存储
-
导入依赖
<!-- 阿里云oss存储api--> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.5.0</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.1</version> </dependency>
-
编写工具类
import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.ObjectMetadata; import java.io.InputStream; import java.net.URL; import java.util.Date; /** * project : blog-ssm * <p> *OSS工具类 * </p> * * @autor:lzj * @date:2022/6/5 */ public class OssManager { private static final String accessKeyId = "LTAI"; //需要修改的 个人开发id private static final String accessKeySecret = "I75"; //需要修改的 开发密匙 /** * 需要修改 * 根据选择的存储空间地点选择:https://help.aliyun.com/document_detail/31837.html?spm=a2c4g.11186623.2.10.5d396a3eVRGHxs#h2-url-1 */ private static final String endpoint = "http://oss-cn-hangzhou.aliyuncs.com"; /** * 需要修改的存储空间的名称 */ public static final String bucket = "********"; /** * bucket域名 */ public static final String bucketDomain="https://********.oss-cn-hangzhou.aliyuncs.com"; private static OSS client; static { client = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); } /** * 上传图片 * @param fileName 图片名称,图片名称包括文件夹名称和“/” * @param length 图片大小 * @param content 输入流 */ public static void uploadImage(String fileName, long length, InputStream content) { uploadBucketImage(bucket, fileName, length, content); } /** * 上传文件 * @param bucket 存储空间名 * @param fileName 文件名(包括文件夹名称和“/”) * @param length 流的长度 * @param content 输入流 */ public static void uploadBucketImage(String bucket, String fileName, long length, InputStream content) { // 创建上传Object的Metadata ObjectMetadata meta = new ObjectMetadata(); // 必须设置ContentLength meta.setContentLength(length); // 上传Object. client.putObject(bucket, fileName, content, meta); } /** * 删除文件 * @param fileName 文件名称,图片名称包括文件夹名称和“/” */ public static boolean delShopImage(String fileName) { //判断文件是否存在 boolean exist = client.doesObjectExist(bucket, fileName); //文件不存在删除失败 if ( ! exist){ return false; } //执行删除 client.deleteObject(bucket, fileName); return true; } /** * 获得上传文件后url链接 * @param fileName 文件名(包括文件夹名称和“/”) * @return */ public static String getUrl(String fileName) { // 设置URL过期时间为10年 3600l* 1000*24*365*10 Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 10); // 生成URL URL url = client.generatePresignedUrl(bucket, fileName, expiration); if (url != null) { return url.toString(); } return null; } /** * 创建存储空间 * @param bucketName 新建存储空间默认为标准存储类型,私有权限。 * @return */ public static void crateBucket(String bucketName) { // 新建存储空间默认为标准存储类型,私有权限。 client.createBucket(bucketName); } }
集成邮件发送服务
-
导入依赖
<!-- 邮件发送--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.3.15</version> </dependency> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version> </dependency>
-
编写配置类
@Configuration public class EmailConfig { @Bean public MailSender mailSender(){ Properties properties = new Properties(); JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); mailSender.setHost("smtp.qq.com"); mailSender.setUsername("1481315703@qq.com"); // 这里不是qq的密码,是邮箱里的密码 mailSender.setPassword("dzrku"); // MailSSLSocketFactory mailSSLSocketFactory = null; // try { // mailSSLSocketFactory = new MailSSLSocketFactory(); // } catch (GeneralSecurityException e) { // e.printStackTrace(); // } // mailSSLSocketFactory.setTrustAllHosts(true); // properties.put("mail.smtp.ssl.socketFactory", mailSSLSocketFactory); // mailSender.setProtocol("smtp"); properties.setProperty("mail.smtp.auth","true"); //设置端口号为587,否则部署在服务器会出错 properties.setProperty("mail.smtp.port", "587"); properties.setProperty("mail.smtp.socketFactory.port", "587"); // properties.setProperty("mail.smtp.socketFactory.fallback", "false"); // properties.put("mail.smtp.ssl.enable", true); // properties.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); mailSender.setJavaMailProperties(properties); return mailSender; } }
-
编写工具类
/** * <p> *邮件工具类 * </p> * * @author:lzj * @date:2022/6/13 */ @Component public class EmailUtil { @Resource private JavaMailSender mailSender; /** * 发送简单邮件 * @param userEmail * @param code */ public void sendSimpleEmail(String userEmail,String code){ SimpleMailMessage message = new SimpleMailMessage();//消息构造器 message.setFrom("1481315703@qq.com");//发件人 message.setTo(userEmail);//收件人 message.setSubject("爱学习的大雄博客登录验证码");//主题 message.setText("您的验证码是:"+code);//正文 mailSender.send(message); } }
集成Mybatis-plus
-
导入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.5.1</version> </dependency>
-
更换连接池配置
<bean class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean" id="sqlSessionFactory">
集成pagehelper
-
导入依赖
<!-- pagehelper--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.0</version> </dependency>
-
在mybatis的配置文件中配置
<!--pagehelper--> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 分页插件会自动检测当前的数据库链接,自动选择合适的分页方式--> <property name="helperDialect" value="mysql"/> <!-- 分页合理化参数,默认值为false。当该参数设置为 true 时, pageNum<=0 时会查询第一页.pageNum>pages(超过总数时), 会查询最后一页。--> <property name="reasonable" value="true"/> <!-- 默认值为 false。设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页--> <property name="autoRuntimeDialect" value="false"/> <!-- 默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果--> <property name="pageSizeZero" value="true"/> <!-- 为了支持startPage(Object params)方法--> <property name="params" value="pageNum=pageNum;pageSize=pageSize;count=countSql"/> </plugin> </plugins>
全部依赖
<dependencies>
<!-- junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- 数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!-- mybatis-->
<!-- <dependency>-->
<!-- <groupId>org.mybatis</groupId>-->
<!-- <artifactId>mybatis</artifactId>-->
<!-- <version>3.5.7</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.mybatis</groupId>-->
<!-- <artifactId>mybatis-spring</artifactId>-->
<!-- <version>2.0.6</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!-- spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.15</version>
</dependency>
<!-- jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.1</version>
</dependency>
<!-- spring实现aop-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<!-- servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!-- jstl-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- standard-->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<!-- jsp-api-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
</dependency>
<!-- rapid-->
<dependency>
<groupId>com.googlecode.rapid-framework</groupId>
<artifactId>rapid-core</artifactId>
<version>4.0.5</version>
</dependency>
<!-- pagehelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
</dependency>
<!-- slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<!-- java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!-- redis-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.1.13</version>
</dependency>
<!-- 阿里云oss存储api-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.1</version>
</dependency>
<!-- swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- 邮件发送-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
项目模块功能
游客用户
主页展示功能
功能介绍:展示网站基本信息,文章信息、网站概况、分类信息、标签信息、公告信息
用户模块
注册功能
功能介绍:
- 用户通过输入用户名、昵称、密码、邮箱来进行账号的注册
- 验证输入信息是否符合要求
- 验证邮箱是否为本人邮箱,判断验证码是否正确
- 判断用户名是否存在
- 新增用户信息,密码加密存储,给前端返回信息
可能产生的异常:
- 邮件发送失败的异常
- 用户名被占用的异常
- 插入数据时产生的异常
Service层
@Override
public void emailSend(String email) {
String code = CurrencyUtil.VerificationCode();
try {
emailUtil.sendSimpleEmail(email,code);
}catch (Exception e){
throw new EmailException("邮件发送出现异常");
}
//将验证码存储至redis,限时60s
redisClient.set(email,code,60);
}
@Override
public void registerUser(String username, String nickname, String email, String password) {
//判断用户名是否存在
if(userMapper.queryUserByUserName(username)!=null){
throw new UsernameDuplicatedException("该用户名已存在");
}
//将数据保存至user对象中,password进行盐值加密返回,盐值为username
User user = new User().setUserName(username).setUserNickname(nickname)
.setUserEmail(email).setUserPass(CurrencyUtil.getMD5Password(password, username))
.setUserRegisterTime(new Date()).setUserRole("user").setUserStatus(1);
user.setUserAvatar(Conts.USERDEFAULTAVATOR);
Integer result = userMapper.insertUser(user);
if (result<=0){
throw new InsertException("注册用户时产生异常");
}
}
controller层
/**
* 用户注册
* @param request 前端提交的请求
* @return
*/
@PostMapping("/registerSubmit")
public JsonResult<Void> registerSubmit(HttpServletRequest request){
String username = request.getParameter("username");
String nickname = request.getParameter("nickname");
String email = request.getParameter("email");
String password = request.getParameter("password");
String code = request.getParameter("code");
if (!code.toUpperCase().equals(redisClient.get(email))){
throw new CodeException("验证码错误");
}
userService.registerUser(username,nickname,email,password);
return new JsonResult<>(OK);
}
/**
* 发送邮件
* @param email
* @return
*/
@PostMapping("/user/emailSend")
public JsonResult<Void> emailSend(String email){
// String email = request.getParameter("email");
//发送邮件
userService.emailSend(email);
return new JsonResult<>(OK);
}
前端页面
表单:
<form name="registerForm" id="registerForm" method="post">
<p>
<label for="username">用户名<br/>
<input type="text" name="username" id="username" class="input"
size="20" required/></label>
</p>
<p>
<label for="nickname">昵称<br/>
<input type="text" name="nickname" id="nickname" class="input"
size="20" required/></label>
</p>
<p>
<label for="password">登录密码<br/>
<input type="password" name="password" id="password" class="input"
size="20" required/>
</label>
</p>
<p>
<label for="confirmPassword">确认密码<br/>
<input type="password" name="confirmPassword" id="confirmPassword" class="input"
size="20" required/>
</label>
</p>
<p>
<label for="email">电子邮箱<br/>
<input type="text" name="email" class="input" id="email"
size="20" required/>
</label>
</p>
<p>
<label for="code">验证码
<input type="text" name="code" id="code" class="input" placeholder="60秒有效" required>
<input type="button" id="code_btn" value="获取邮箱验证码">
</label>
</p>
<p class="submit">
<input type="button" name="wp-submit" id="submit-btn" class="button button-primary button-large"
value="注册"/>
</p>
</form>
javascript:
<script src="/js/jquery.min.js"></script>
<script type="text/javascript">
<%-- 发送验证码--%>
$("#code_btn").click(function (){
var email = $("#email").val();
var user = $("#username").val();
var nickname = $("#nickname").val();
var password = $("#password").val();
var confirmPass = $("#confirmPassword").val();
let reg= /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
if (user == "" || nickname == "" || email == "" || password == "" || confirmPass == "") {
alert("请输入完整信息!")
} else if (password != confirmPass) {
alert("两次密码不一致!");
} else if(user.length < 4 || user.length > 20) {
alert('用户名长度不合法');
} else if(!reg.test(email)){
alert('邮箱格式不合法');
}else{
var time=60;
$(this).attr("disabled",true);
var timer = setInterval(function () {
if(time == 0){
$("#code_btn").removeAttr("disabled");
$("#code_btn").html("重新发送");
$("#code_btn").val("重新发送");
clearInterval(timer);
}else {
$("#code_btn").html(time);
$("#code_btn").val("剩余"+time+"秒");
time--;
}
},1000);
$.ajax({
// async: false,//同步,待请求完毕后再执行后面的代码
type: "POST",
url: '/user/emailSend',
// contentType: "application/x-www-form-urlencoded;charset=utf-8",
// contentType: "application/json",
data: {
"email": email
},
dataType: "json",
success: function (data) {
if (data.state == 200) {
alert('发送成功');
} else {
alert(data.message);
}
},
error: function (xhr) {
alert("数据获取异常");
}
})
}
})
<%--注册验证--%>
$("#submit-btn").click(function () {
var user = $("#username").val();
var email = $("#email").val();
var nickname = $("#nickname").val();
var password = $("#password").val();
var confirmPass = $("#confirmPassword").val();
var code=$("#code").val();
let reg= /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
if (user == "" || nickname == "" || email == "" || password == "" || confirmPass == "") {
alert("请输入完整信息!")
} else if (password != confirmPass) {
alert("两次密码不一致!");
} else if(user.length < 4 || user.length > 20) {
alert('用户名长度不合法');
} else if(!reg.test(email)){
alert('邮箱格式不合法');
} else if (code==""){
alert("请验证邮箱");
}
else {
$.ajax({
async: false,//同步,待请求完毕后再执行后面的代码
type: "POST",
url: '/registerSubmit',
contentType: "application/x-www-form-urlencoded;charset=utf-8",
data: $("#registerForm").serialize(),
dataType: "json",
success: function (data) {
if (data.state == 200) {
alert('注册成功');
window.location.href = "/login";
} else {
alert(data.message);
}
},
error: function (xhr) {
alert("数据获取失败")
}
})
}
})
</script>
文章模块
文章游览功能
功能介绍:
用户游览文章时,文章游览量会增加
用户可以查看到文章的分类和标签
可以游览到该文章下所有评论
若用户处于登陆状态,则可以进行评论,否则不能进行评论
Controller层
/**
* 文章游览数量
* @param id 文章id
* @return
*/
@RequestMapping(value = "/article/view/{id}", method = {RequestMethod.POST})
@ResponseBody
public JsonResult<Integer> increaseViewCount(@PathVariable("id") Integer id) {
Article article = articleService.getArticleByStatusAndId(ArticleStatus.PUBLISH.getValue(), id);
Integer articleCount = article.getArticleViewCount() + 1;
article.setArticleViewCount(articleCount);
articleService.updateArticleViewCount(article);
return new JsonResult<>(OK,articleCount);
}
/**
* 通过文章id获取文章信息
* @param articleId
* @param model
* @return
*/
@RequestMapping("/article/{articleId}")
public String getArticleDetailPage(@PathVariable("articleId") Integer articleId, Model model){
Article article = articleService.getArticleByStatusAndId(ArticleStatus.PUBLISH.getValue(), articleId);
if (article == null) {
return "redirect:/Error/404";
}
//文章信息
model.addAttribute("article", article);
//评论信息
List<Comment> comments = commentService.listCommentByArticleId(articleId);
model.addAttribute("commentList", comments);
//相关文章
List<Category> categoryList = article.getCategoryList();
ArrayList<Integer> categoryIds = new ArrayList<>();
for (Category category : categoryList) {
categoryIds.add(category.getCategoryId());
}
List<Article> similarArticleList = articleService.listArticleByCategoryIds(categoryIds, 5);
model.addAttribute("similarArticleList", similarArticleList);
//猜你喜欢
List<Article> mostViewArticleList = articleService.listArticleByViewCount(5);
model.addAttribute("mostViewArticleList", mostViewArticleList);
//热评文章
List<Article> mostCommentArticleList = articleService.listArticleByCommentCount(8);
model.addAttribute("mostCommentArticleList", mostCommentArticleList);
//所有标签
List<Tag> tags = tagService.listTag();
model.addAttribute("allTagList", tags);
//随机文章
List<Article> randomArticleList = articleService.listRandomArticle(8);
model.addAttribute("randomArticleList", randomArticleList);
//上一篇文章
Article preArticle = articleService.getPreArticle(articleId);
model.addAttribute("preArticle", preArticle);
//下一篇文章
Article afterArticle = articleService.getAfterArticle(articleId);
model.addAttribute("afterArticle", afterArticle);
return "Home/Page/articleDetail";
}
普通用户
用户模块
登录功能
功能介绍:
- 用户输入用户名、密码、验证码、是否记住密码进行登录
- 验证输入信息是否符合要求
- 判断用户是否存在,密码加密匹配,用户是否被禁用
- 使用JWT生成token,荷载用户信息
- 使用cookie存储用户id,redis存储生成token
- 判断是否记住密码,若记住密码则将信息存储至cookie
- 记录用户的最后登录时间和最后登录ip
可能产生的异常:
- 用户名不存在的异常
- 密码匹配失败的异常
- 用户名被禁用的异常
- 更新用户数据时产生的异常
Service层
@Override
public void userLogin(String username, String password, String rememberme, HttpServletRequest request, HttpServletResponse response) {
//进入方法
User user = userMapper.queryUserByUserName(username);
//判断用户是否存在
if (user==null){
throw new UserNotFoundException("该用户不存在");
}
//判断密码是否匹配
if (!user.getUserPass().equals(CurrencyUtil.getMD5Password(password,username))){
throw new PasswordNotMatchException("账户与密码不匹配");
}
// if (!user.getUserPass().equals(password)){
// throw new PasswordNotMatchException("账户与密码不匹配");
// }
//判断用户是否被禁用
if(user.getUserStatus()==0){
throw new StatusException("用户状态异常");
}
HashMap<String, String> map = new HashMap<String, String>();
map.put("userName",username);
map.put("userId", String.valueOf(user.getUserId()));
map.put("userRole",user.getUserRole());
map.put("userAvatar",user.getUserAvatar());
map.put("userNickname",user.getUserNickname());
map.put("userUrl",user.getUserUrl());
map.put("userEmail",user.getUserEmail());
String token = JWTUtils.getToken(map);
//将token存入redis中
redisTemplate.opsForValue().set(String.valueOf(user.getUserId()),token,7, TimeUnit.DAYS);
//将uid存入cookie中
CookieUtil.addCookie(response,"uid", String.valueOf(user.getUserId()),60*60*24*7);
//如果选择了记住我,则将用户名和密码放入cookie中
if (rememberme!=null){
//创建两个Cookie对象
Cookie nameCookie = new Cookie("username", username);
//设置Cookie的有效期为3天
nameCookie.setMaxAge(60 * 60 * 24 * 7);
Cookie pwdCookie = new Cookie("password", password);
pwdCookie.setMaxAge(60 * 60 * 24 * 7);
response.addCookie(nameCookie);
response.addCookie(pwdCookie);
}else{//删除原来选择的记住我信息
Cookie usernameCookie = CookieUtil.getCookieByName(request, "username");
Cookie passwordCookie = CookieUtil.getCookieByName(request, "password");
if (usernameCookie!=null){
CookieUtil.removeCookie(response,"username");
}
if (passwordCookie!=null){
CookieUtil.removeCookie(response,"password");
}
}
user.setUserLastLoginTime(new Date()).setUserLastLoginIp(CurrencyUtil.getIpAddr(request));
Integer result = userMapper.updateUser(user);
if (result<=0){
throw new UpdateException("更新用户信息时产生异常");
}
}
controller层
/**
* 用户登录
* @param request 前端请求
* @param response 前端响应
* @return
*/
@PostMapping("/loginVerify")
public JsonResult<Void> loginVerify(HttpServletRequest request, HttpServletResponse response){
String username = request.getParameter("username");
String password = request.getParameter("password");
String rememberme = request.getParameter("rememberme");
userService.userLogin(username,password,rememberme,request,response);
return new JsonResult<>(OK);
}
前端页面
表单
<%
String username = "";
String password = "";
//获取当前站点的所有Cookie
Cookie[] cookies = request.getCookies();
for (int i = 0; i < cookies.length; i++) {//对cookies中的数据进行遍历,找到用户名、密码的数据
if ("username".equals(cookies[i].getName())) {
username = cookies[i].getValue();
} else if ("password".equals(cookies[i].getName())) {
password = cookies[i].getValue();
}
}
%>
<form name="loginForm" id="loginForm" method="post">
<p>
<label for="user_login">用户名<br />
<input type="text" name="username" id="user_login" class="input" value="<%=username%>" size="20" required/></label>
</p>
<p>
<label for="user_pass">密码<br />
<input type="password" name="password" id="user_pass" class="input" value="<%=password%>" size="20" required/>
</label>
</p>
<p>
<label for="vcode">验证码<br />
<input type="text" name="vcode" id="vcode" placeholder="验证码" value="验证码" onfocus="this.value=''" onblur="if(this.value=='')this.value='验证码'"/>
<span id="code" title="看不清,换一张"></span>
</label>
<div id="search_pass_link"></div>
</p>
<p class="forgetmenot"><label for="rememberme"><input name="rememberme" type="checkbox" id="rememberme" value="1" checked /> 记住密码</label></p>
<p class="submit">
<input type="button" name="wp-submit" id="submit-btn" class="button button-primary button-large" value="登录" />
</p>
</form>
javascript
<script type="text/javascript">
function wp_attempt_focus(){
setTimeout( function(){ try{
d = document.getElementById('user_login');
d.focus();
d.select();
} catch(e){}
}, 200);
}
wp_attempt_focus();
if(typeof wpOnload=='function')wpOnload();
</script>
<p id="backtoblog"><a href="/">← 返回到博客首页</a> | <a href="/register">注册</a> | <a href="/user/forgetPasswordView">忘记密码</a> </p>
</div>
<div class="clear"></div>
<script src="/js/jquery.min.js"></script>
<script type="text/javascript">
let code; //声明一个变量用于存储生成的验证码
document.getElementById("code").onclick = changeImg;
function changeImg() {
// alert("进入了验证码");
var arrays = new Array(
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'
);
code = ''; //重新初始化验证码
//alert(arrays.length);
//随机从数组中获取四个元素组成验证码
for(var i = 0; i < 4; i++) {
//随机获取一个数组的下标
var r = parseInt(Math.random() * arrays.length);
code += arrays[r];
}
// alert(code);
document.getElementById('code').innerHTML = code; //将验证码写入指定区域
}
// //效验验证码(表单被提交时触发)
// function check() {
// //获取用户输入的验证码
// var input_code = document.getElementById('vcode').value;
// if(input_code.toLowerCase() == code.toLowerCase()) {
// //验证码正确(表单提交)
// return true;
// }
// alert("请输入正确的验证码!");
// //验证码不正确,表单不允许提交
// return false;
// }
<%--登录验证--%>
$("#submit-btn").click(function () {
var user = $("#user_login").val();
var password = $("#r_pass").val();
var input_code= $("#vcode").val();
if(user=="") {
alert("用户名不可为空!");
} else if(password==""){
alert("密码不可为空!");
} else if (input_code.toLowerCase() != code.toLowerCase()){
alert("输入的验证码错误!");
}
else {
$.ajax({
async: false,//同步,待请求完毕后再执行后面的代码
type: "POST",
url: '/loginVerify',
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: $("#loginForm").serialize(),
dataType: "json",
success: function (data) {
if(data.state==200) {
window.location.href="/admin";
} else {
alert(data.message);
}
},
error: function () {
alert("数据获取失败")
}
})
}
})
</script>
忘记密码
功能介绍:
- 用户忘记密码可通过输入用户名和邮箱来进行密码重置
- 邮箱发送验证码,用户将其填写提交,后端判断是否正确
- 信息正确,设置一个cookie存储用户输入的用户名,跳转至修改密码界面
- 用户输入新密码,确定新密码,点击提交
- 密码加密存储,用户密码修改成功,跳转至登陆界面
可能出现的异常:
- 邮箱验证码不匹配的异常
- 邮箱和用户名不匹配的异常
- cookie数据过期的异常
Service层
@Override
public void forgetPasswordVerify(String email, String username) {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("user_name",username).select("user_email");
User user = userMapper.selectOne(userQueryWrapper);
if(!user.getUserEmail().equals(email)){
throw new RoleErrorException("用户名与邮箱不匹配");
}
}
@Override
public void userUpdatePassword(String newPassword, String oldPassword, HttpServletRequest request, HttpServletResponse response) {
User user=null;
if (oldPassword!=null){//如果用户修改密码
//获取用户id
Cookie uid = CookieUtil.getCookieByName(request, "uid");
String uidValue = uid.getValue();
user = userMapper.selectById(Integer.valueOf(uidValue));
if (!user.getUserPass().equals(CurrencyUtil.getMD5Password(oldPassword,user.getUserName()))){
throw new PasswordNotMatchException("密码匹配失败!");
}
//将新密码插入
user.setUserPass(CurrencyUtil.getMD5Password(newPassword,user.getUserName()));
//更新用户信息
userMapper.updateById(user);
//退出登录
userLoginOut(request,response);
CookieUtil.removeCookie(response,"password");
}else {//如果用户忘记密码
Cookie restname = CookieUtil.getCookieByName(request, "restname");
if (restname!=null){
String value = restname.getValue();
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper.eq("user_name",value);
user.setUserPass(CurrencyUtil.getMD5Password(newPassword,value));
userMapper.update(user,userUpdateWrapper);
}else {
throw new NotFountException("未找到cookie");
}
}
}
Controller层
/**
* 发送邮件
* @param email
* @return
*/
@PostMapping("/user/emailSend")
public JsonResult<Void> emailSend(String email){
// String email = request.getParameter("email");
//发送邮件
userService.emailSend(email);
return new JsonResult<>(OK);
}
/**
* 跳转用户忘记密码的修改密码页面
*/
@PostMapping("/user/forgetPasswordVerify")
public JsonResult<Void> forgetPasswordVerify(String email,String username,String code,HttpServletResponse response){
if(!code.toUpperCase().equals(redisClient.get(email))){
throw new CodeException("验证码错误");
}
userService.forgetPasswordVerify(email,username);
//设置cookie5分钟有效
CookieUtil.addCookie(response,"restname",username,60*5);
return new JsonResult<>(OK);
}
@PostMapping("/user/updatePassword")
public JsonResult<Void> updatePassword(HttpServletRequest request,HttpServletResponse response){
String oldPassword = request.getParameter("oldPassword");
String newPassword = request.getParameter("newPassword");
//如果是重写密码则oldPassword为null
userService.userUpdatePassword(newPassword,oldPassword,request,response);
return new JsonResult<>(OK);
}
修改密码
功能介绍:
- 用户处于登陆状态,输入原来密码,输入修改的密码,输入确定密码,点击提交
- 旧密码匹配判断
- 修改用户密码
- 退出登录,移除密码的cookie
可能出现的异常:
- 密码匹配失败的异常
Service层
@Override
public void userUpdatePassword(String newPassword, String oldPassword, HttpServletRequest request, HttpServletResponse response) {
User user=null;
if (oldPassword!=null){//如果用户修改密码
//获取用户id
Cookie uid = CookieUtil.getCookieByName(request, "uid");
String uidValue = uid.getValue();
user = userMapper.selectById(Integer.valueOf(uidValue));
if (!user.getUserPass().equals(CurrencyUtil.getMD5Password(oldPassword,user.getUserName()))){
throw new PasswordNotMatchException("密码匹配失败!");
}
//将新密码插入
user.setUserPass(CurrencyUtil.getMD5Password(newPassword,user.getUserName()));
//更新用户信息
userMapper.updateById(user);
//退出登录
userLoginOut(request,response);
CookieUtil.removeCookie(response,"password");
}else {//如果用户忘记密码
Cookie restname = CookieUtil.getCookieByName(request, "restname");
if (restname!=null){
String value = restname.getValue();
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper.eq("user_name",value);
user.setUserPass(CurrencyUtil.getMD5Password(newPassword,value));
userMapper.update(user,userUpdateWrapper);
}else {
throw new NotFountException("未找到cookie");
}
}
}
Controller层
/**
* 用户提交修改密码
* @param request
* @param response
* @return
*/
@PostMapping("/user/updatePassword")
public JsonResult<Void> updatePassword(HttpServletRequest request,HttpServletResponse response){
String oldPassword = request.getParameter("oldPassword");
String newPassword = request.getParameter("newPassword");
//如果是重写密码则oldPassword为null
userService.userUpdatePassword(newPassword,oldPassword,request,response);
return new JsonResult<>(OK);
}
修改基本信息
功能介绍:
- 用户处于登陆状态,需要修改头像、url、昵称
- 点击进入用户信息修改页面,页面将其数据展示出来
- 点击上传图片,图片信息保存至本地,图片存储路径返回至前端页面
- 修改url或昵称,点击提交
- 通过cookid中的uid获取存储在reidis中的token,获取其中的荷载信息
- 重新将修改后的用户信息荷载生成token存入redis中
- 更新用户信息
可能出现的异常:
- 用户的token不存在的异常
Service层
@Override
public void updateUser(User user) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("userName",user.getUserName());
map.put("userId", String.valueOf(user.getUserId()));
map.put("userRole",user.getUserRole());
map.put("userAvatar",user.getUserAvatar());
map.put("userNickname",user.getUserNickname());
map.put("userUrl",user.getUserUrl());
map.put("userEmail",user.getUserEmail());
String token = JWTUtils.getToken(map);
//将更新后的token存入redis中
redisTemplate.opsForValue().set(String.valueOf(user.getUserId()),token,7, TimeUnit.DAYS);
userMapper.updateUser(user);
}
Controller层
/**
* 跳转用户信息修改页面
* @param request
* @return
*/
@RequestMapping("/admin/profile/edit")
public ModelAndView editUserView(HttpServletRequest request){
ModelAndView modelAndView = new ModelAndView();
Cookie uid = CookieUtil.getCookieByName(request, "uid");
String uidValue = uid.getValue();
User userById = userService.getUserById(Integer.valueOf(uidValue));
modelAndView.addObject("user", userById);
modelAndView.setViewName("Admin/User/editProfile");
return modelAndView;
}
/**
* 编辑用户信息提交
*/
@PostMapping("/admin/profile/save")
public ModelAndView editUserSubmit(User user,HttpServletRequest request){
ModelAndView modelAndView = new ModelAndView();
Cookie uid = CookieUtil.getCookieByName(request, "uid");
String uidValue = uid.getValue();
String toke = (String) redisClient.get(uidValue);
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(toke);
user.setUserRole(tokenInfo.getClaim("userRole").asString());
user.setUserEmail(tokenInfo.getClaim("userEmail").asString());
user.setUserUrl(tokenInfo.getClaim("userUrl").asString());
userService.updateUser(user);
modelAndView.setViewName("redirect:/admin/profile");
return modelAndView;
}
文章模块
文章编写功能
功能介绍:
- 用户写入文章标题、内容,选择分类、标签、上传文章封面图片,选择文章状态(发布或草稿)
- 前端自带markdown编辑器会将输入的内容转换为html的数据格式传入后台
- 后台通过cookie获取用户id,将文章分类信息、文章标签信息、文章信息存入article对象中
- 文章信息写入数据库中时,同时将标签信息写入写入标签-文章关联表、分类信息写入分类-文章关联表
Controller层
/**
* 后台添加文章提交操作
*
* @param articleParam
* @return
*/
@RequestMapping(value = "/insertSubmit", method = RequestMethod.POST)
public String insertArticleSubmit(HttpServletRequest request, ArticleParam articleParam) {
Article article = new Article();
//用户ID
Cookie uid = CookieUtil.getCookieByName(request, "uid");
if (uid != null) {
article.setArticleUserId(Integer.valueOf(uid.getValue()));
}
//文章标题
article.setArticleTitle(articleParam.getArticleTitle());
//文章摘要
int summaryLength = 130;
String summaryText = HtmlUtil.cleanHtmlTag(articleParam.getArticleContent());
if (summaryText.length() > summaryLength) {
String summary = summaryText.substring(0, summaryLength);
article.setArticleSummary(summary);
} else {
article.setArticleSummary(summaryText);
}
article.setArticleThumbnail(articleParam.getArticleThumbnail());
article.setArticleContent(articleParam.getArticleContent());
article.setArticleStatus(articleParam.getArticleStatus());
//填充分类
List<Category> categoryList = new ArrayList<>();
if (articleParam.getArticleChildCategoryId() != null) {
categoryList.add(new Category(articleParam.getArticleParentCategoryId()));
}
if (articleParam.getArticleChildCategoryId() != null) {
categoryList.add(new Category(articleParam.getArticleChildCategoryId()));
}
article.setCategoryList(categoryList);
//填充标签
List<Tag> tagList = new ArrayList<>();
if (articleParam.getArticleTagIds() != null) {
for (int i = 0; i < articleParam.getArticleTagIds().size(); i++) {
Tag tag = new Tag(articleParam.getArticleTagIds().get(i));
tagList.add(tag);
}
}
article.setTagList(tagList);
articleService.insertArticle(article);
return "redirect:/admin/article";
}
Service层
@Override
public void insertArticle(Article article) {
//添加文章
article.setArticleCreateTime(new Date());
article.setArticleUpdateTime(new Date());
article.setArticleIsComment(ArticleCommentStatus.ALLOW.getValue());
article.setArticleViewCount(0);
article.setArticleLikeCount(0);
article.setArticleCommentCount(0);
article.setArticleOrder(1);
articleMapper.insert(article);
//添加分类和文章关联
for (int i = 0; i < article.getCategoryList().size(); i++) {
ArticleCategoryRef articleCategoryRef = new ArticleCategoryRef(article.getArticleId(), article.getCategoryList().get(i).getCategoryId());
articleCategoryRefMapper.insert(articleCategoryRef);
}
//添加标签和文章关联
for (int i = 0; i < article.getTagList().size(); i++) {
ArticleTagRef articleTagRef = new ArticleTagRef(article.getArticleId(), article.getTagList().get(i).getTagId());
articleTagRefMapper.insert(articleTagRef);
}
}
文章编辑功能
功能介绍:
用户可以对自己发布或编写的文章进行重新编辑发布
功能完成:
- 原文章数据的展示
- 修改数据点击提交
- 后台删除文章-分类关联表、标签-文章关联表中本文章的数据,插入新数据
- 更新文章表数据
controller层
/**
* 编辑文章页面
* @param id
* @param model
* @param request
* @return
*/
@RequestMapping(value = "/edit/{id}")
public String editArticleView(@PathVariable("id") Integer id, Model model, HttpServletRequest request) {
Article article = articleService.getArticleByStatusAndId(null, id);
if (article == null) {
return "redirect:/Error/404";
}
Cookie uid = CookieUtil.getCookieByName(request, "uid");
String uidValue = uid.getValue();
String token = (String) redisClient.get(uidValue);
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
String userRole = tokenInfo.getClaim("userRole").asString();
// 如果不是管理员,访问其他用户的数据,则跳转403
if (!article.getArticleUserId().equals(Integer.valueOf(uidValue)) && !userRole.equals(UserRole.ADMIN.getValue())) {
return "redirect:/Error/403";
}
model.addAttribute("article", article);
//获取分类
List<Category> categoryList = categoryService.listCategory();
model.addAttribute("categoryList", categoryList);
//获取标签
List<Tag> tagList = tagService.listTag();
model.addAttribute("tagList", tagList);
return "Admin/Article/edit";
}
/**
* 更新文章提交
* @param articleParam
* @param request
* @return
*/
@RequestMapping(value = "/editSubmit", method = RequestMethod.POST)
public String editArticleSubmit(ArticleParam articleParam, HttpServletRequest request) {
Article dbArticle = articleService.getArticleByStatusAndId(null, articleParam.getArticleId());
if (dbArticle == null) {
return "redirect:/Error/404";
}
Cookie uid = CookieUtil.getCookieByName(request, "uid");
String uidValue = uid.getValue();
String token = (String) redisClient.get(uidValue);
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
String userRole = tokenInfo.getClaim("userRole").asString();
// 如果不是管理员,访问其他用户的数据,则跳转403
if (!dbArticle.getArticleUserId().equals(Integer.valueOf(uidValue)) && !userRole.equals(UserRole.ADMIN.getValue())) {
return "redirect:/Error/403";
}
Article article = new Article();
article.setArticleThumbnail(articleParam.getArticleThumbnail());
article.setArticleId(articleParam.getArticleId());
article.setArticleTitle(articleParam.getArticleTitle());
article.setArticleContent(articleParam.getArticleContent());
article.setArticleStatus(articleParam.getArticleStatus());
//文章摘要
int summaryLength = 150;
String summaryText = HtmlUtil.cleanHtmlTag(article.getArticleContent());
if (summaryText.length() > summaryLength) {
String summary = summaryText.substring(0, summaryLength);
article.setArticleSummary(summary);
} else {
article.setArticleSummary(summaryText);
}
//填充分类
List<Category> categoryList = new ArrayList<>();
if (articleParam.getArticleChildCategoryId() != null) {
categoryList.add(new Category(articleParam.getArticleParentCategoryId()));
}
if (articleParam.getArticleChildCategoryId() != null) {
categoryList.add(new Category(articleParam.getArticleChildCategoryId()));
}
article.setCategoryList(categoryList);
//填充标签
List<Tag> tagList = new ArrayList<>();
if (articleParam.getArticleTagIds() != null) {
for (int i = 0; i < articleParam.getArticleTagIds().size(); i++) {
Tag tag = new Tag(articleParam.getArticleTagIds().get(i));
tagList.add(tag);
}
}
article.setTagList(tagList);
article.setArticleUpdateTime(new Date());
articleService.updateArticleDetail(article);
return "redirect:/admin/article";
}
文章删除功能
功能介绍:
用户通过点击删除按钮可以删除自己发布的文章数据
功能完成:
- 后端接收前端传来的文章id
- 查询该文章id是否存在
- 判断用户权限,是否为博主或管理员
- 删除文章表中的数据,删除分类关联,删除标签关联、删除评论关联
Controller层
/**
* 删除文章
* @param id
* @param request
*/
@RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
public JsonResult<String> deleteArticle(@PathVariable("id") Integer id, HttpServletRequest request) {
Article dbArticle = articleService.getArticleByStatusAndId(null, id);
if (dbArticle == null) {
throw new NotFountException("文章信息未找到");
}
Cookie uid = CookieUtil.getCookieByName(request, "uid");
String uidValue = uid.getValue();
String token = (String) redisClient.get(uidValue);
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
String userRole = tokenInfo.getClaim("userRole").asString();
// 如果不是管理员,访问其他用户的数据,则跳转403
if (!dbArticle.getArticleUserId().equals(Integer.valueOf(uidValue)) && !userRole.equals(UserRole.ADMIN.getValue())) {
throw new RoleErrorException("用户权限异常");
}
articleService.deleteArticle(id);
return new JsonResult<>(OK);
}
Service层
@Override
public void deleteArticle(Integer articleId) {
QueryWrapper<Article> articleQueryWrapper = new QueryWrapper<>();
articleQueryWrapper.eq("article_id",articleId);
HashMap<String, Object> map = new HashMap<>();
map.put("article_id",articleId);
articleMapper.delete(articleQueryWrapper);
//删除分类关联
articleCategoryRefMapper.deleteByMap(map);
//删除标签关联
articleTagRefMapper.deleteByMap(map);
//删除评论关联
// HashMap<String, Object> map2 = new HashMap<>();
// map.put("comment_article_id",articleId);
QueryWrapper<Comment> commentQueryWrapper = new QueryWrapper<>();
commentQueryWrapper.eq("comment_article_id",articleId);
commentMapper.delete(commentQueryWrapper);
}
评论模块
评论他人功能
功能介绍:
用户游览文章时,如果处于登陆状态,则可对文章进行评论
功能完成:
- 判断验证用户是否处于登陆状态
- 判断文章是否存在,判断评论人id和博主id是否同一个
- 添加评论,更新文章的评论数量
Controller层
@RequestMapping(value = "/comment", method = {RequestMethod.POST})
@ResponseBody
public JsonResult<String> insertComment(HttpServletRequest request, Comment comment) {
System.out.println("进入了方法");
System.out.println(comment);
Cookie uid = CookieUtil.getCookieByName(request, "uid");
if (uid == null) {
String data="请先登录";
return new JsonResult<>(OK,data);
}
String uidValue = uid.getValue();
User user = userService.getUserById(Integer.valueOf(uidValue));
Article article = articleService.getArticleByStatusAndId(ArticleStatus.PUBLISH.getValue(), comment.getCommentArticleId());
if (article == null) {
String data="文章不存在";
return new JsonResult<>(OK,data);
}
//添加评论
comment.setCommentUserId(user.getUserId());
comment.setCommentCreateTime(new Date());
comment.setCommentIp(CurrencyUtil.getIpAddr(request));
//比较评论人id和文章用户id
if (Objects.equals(user.getUserId(), article.getArticleUserId())) {
comment.setCommentRole(Role.OWNER.getValue());
} else {
comment.setCommentRole(Role.VISITOR.getValue());
}
comment.setCommentAuthorAvatar(user.getUserAvatar());
//过滤字符,防止XSS攻击
comment.setCommentContent(HtmlUtil.escape(comment.getCommentContent()));
comment.setCommentAuthorName(user.getUserNickname());
comment.setCommentAuthorEmail(user.getUserEmail());
comment.setCommentAuthorUrl(user.getUserUrl());
commentService.insertComment(comment);
//更新文章的评论数
articleService.updateCommentCount(article.getArticleId());
return new JsonResult<>(OK);
}
查看自己文章的全部评论
功能介绍:
用户通过进入后台页面,可以查看到自己发布文章的全部评论信息
Controller层
/**
*前端页面,我收到的评论
* @param pageIndex
* @param request
* @param model
* @return
*/
@RequestMapping("/receive")
public String receive(@RequestParam(required = false, defaultValue = "1") Integer pageIndex,HttpServletRequest request,Model model){
//从cookie中取到用户的id
Cookie uid = CookieUtil.getCookieByName(request, "uid");
String cookieUid = uid.getValue();
PageInfo<Comment> commentPageInfo = commentService.listReceiveCommentByPage(pageIndex, Conts.PAGESIZE, Integer.valueOf(cookieUid));
model.addAttribute("pageInfo", commentPageInfo);
model.addAttribute("pageUrlPrefix", "/admin/comment?pageIndex");
return "Admin/Comment/index";
}
Service层
@Override
public PageInfo<Comment> listReceiveCommentByPage(Integer pageIndex, Integer pagesize, Integer userId) {
//查询我的所有文章id
QueryWrapper<Article> articleQueryWrapper = new QueryWrapper<>();
articleQueryWrapper.select("article_id").eq("article_user_id",userId);
List<Article> articles = articleMapper.selectList(articleQueryWrapper);
PageHelper.startPage(pageIndex,pagesize);
List<Comment> comments=new ArrayList<>();
if (articles!=null&&articles.size()>0){
// QueryWrapper<Comment> commentQueryWrapper = new QueryWrapper<>();
// commentQueryWrapper.in("comment_article_id",articles).orderByDesc("comment_id");
// comments = commentMapper.selectList(commentQueryWrapper);
// PageInfo<Comment> commentPageInfo = new PageInfo<>(comments);
comments = commentMapper.getReceiveComment(articles);
}
PageInfo<Comment> commentPageInfo = new PageInfo<>(comments);
return commentPageInfo;
}
查看自己发布的全部评论
功能介绍:
用户进入后台界面,可以查看到自己发布的全部评论信息
Controller层
/**
* 普通用户展示自己的评论,管理员用户展示全部评论
* @param model
* @param pageIndex
* @param request
* @return
*/
@RequestMapping("")
public String index(Model model, @RequestParam(required = false, defaultValue = "1") Integer pageIndex, HttpServletRequest request){
HashMap<String, Object> criteria = new HashMap<>();
//从cookie中取到用户的id
Cookie uid = CookieUtil.getCookieByName(request, "uid");
String cookieUid = uid.getValue();
//根据用户id在redis中取到token
String token = (String) redisClient.get(cookieUid);
//获取token中的荷载信息
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
String userRole = tokenInfo.getClaim("userRole").asString();
if (!UserRole.ADMIN.getValue().equals(userRole)) {
// 用户查询自己的文章, 管理员查询所有的
criteria.put("userId", Integer.valueOf(cookieUid));
}
PageInfo<Comment> commentPageInfo = commentService.listCommentByPage(pageIndex, Conts.PAGESIZE, criteria);
model.addAttribute("pageInfo", commentPageInfo);
model.addAttribute("pageUrlPrefix", "/admin/comment?pageIndex");
return "Admin/Comment/index";
}
管理员用户
用户模块
管理用户信息
功能介绍:
- 管理员可查询全部用户信息
- 管理员用户可修改用户的状态
- 管理员用户可重置用户的密码
- 管理员用户可删除用户的信息
文章模块
管理文章信息
功能介绍:
- 管理员可查询全部文章信息
- 管理员可修改文章状态
- 管理员可编辑所有文章
- 管理员可删除所有文章
分类模块
管理分类信息
功能介绍:
- 展示全部分类信息
- 编辑分类信息
- 新增分类信息
- 删除分类信息
评论模块
管理评论信息
功能介绍:
- 展示全部评论
- 编辑评论
- 回复评论
- 删除评论
链接模块
管理链接信息
功能介绍:
- 展示全部链接
- 编辑链接
- 新增链接
- 删除链接
菜单模块
菜单信息管理
功能介绍:
- 展示全部菜单
- 新增菜单信息
- 编辑菜单信息
- 删除菜单信息
公告模块
公告信息管理
功能介绍:
- 展示全部公告信息
- 新增公告信息
- 编辑公告信息
- 删除公告信息
页面模块
页面信息管理
功能介绍:
- 展示全部页面信息
- 新增页面信息
- 编辑页面信息
- 删除页面信息
标签模块
管理标签信息
功能介绍:
- 展示全部标签信息
- 新增标签信息
- 编辑标签信息
- 删除标签信息
网站信息模块
管理网站基本信息
功能介绍:
- 展示网站基本信息
- 修改网站基本信息
项目部署
前提:Linux中已经配置好JDK、tomcat、nginx等环境
本项目是ssm项目,通过打war包的方式部署至LInux云服务器中,通过nginx+tomcat的方式去部署
-
在idea中进行打war包的操作
进入Project Structure ->Artifacts
查看是否有blog-ssm:war exploded
点击主界面上方的Build
点击之后,项目中会生成一个out包,这个时候需要点开out包检查一下是不是所有的类都打包进去了,如果没有,则需要进行重新打包,否则在部署项目时会出现访问不了的问题
打好的war包就是如下这个
-
Linux服务器中复制tomcat为tomcat1、tomcat2
cp -r
原目录名
需要复制到的路径
-
上传打好的war包至tomcat1、tomcat2下的webapps目录下,将原来的ROOT目录删除,将上传的war包修改名为ROOT
-
修改tomcat的配置(conf目录下server.xml)
修改其中的三个端口号,每个tomcat的这3个端口号必须不一样
-
启动tomcat(bin目录下的starup.sh)
-
修改nginx的配置
-
启动nginx即可