基于SSM框架实现后台数据管理系统

目录

一. 前言

二. 主要技术栈

三.项目搭建

3.1 数据库搭建

3.2 maven项目搭建

四、实现基本的CRUD操作

五、SpringAOP实现记录日志

六、SpringMVC拦截器interceptor拦截用户的权限

七、Bcrypt加密算法

八、总结


一. 前言

本项目的客户端是基于开源项目AdminLTE 和AdminLTE 黑马定制版进行编写。AdminLTE 是一个完全的响应式管理模板,基于Bootstrap3框架,高度可定制,易于使用。适合从小型移动设备到大型台式机的屏幕分辨率 。

本项目可实现用户登录与头像的上传,根据用户的角色与权限不同,操作不同的模块进行基本的CRUD操作。实现对用户的请求进行日志记录,对用户的权限进行监控与拦截,用户在没有登录的情况下,不能对后台进行访问操作,当点击菜单就会跳转到登录页面。只有用户登录成功,才能进行后台功能的操作。对用户的密码进行加密与解密,整合redis实现mybatis的二级缓存。

 


二. 主要技术栈

  • 前端页面:Bootstrap3框架,Jquery,JSP
  • 后端:JavaEE,SSM框架,redis,spring-security,bcrypt加密算法

开发工具:jdk8,idea2022,navicat,redis,mysql8,maven仓库,firefox/chorm


三.项目搭建

3.1 数据库搭建

打开navicat,创建名为ssm的数据库,导入准备好的sql脚本文件,数据库模型图如下所示,共11张表。用户表通过中间表与角色表相连,角色表通过中间表与权限表相连;订单表通过外键与订单负责人相连、通过中间表与旅客表进行相连。syslog为日志记录表,product为产品表。

3.2 maven项目搭建

打开idea新建maven工程,在pom文件中添加对应的依赖,具体的依赖如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jbz</groupId>
    <artifactId>ssm</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>ssm</name>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <!--Spring的使用版本-->
        <spring.version>5.0.5.RELEASE</spring.version>
    </properties>

    <dependencies>
        <!--junit4依赖坐标-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--servlet坐标-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!--jsp坐标-->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.2.1</version>
            <scope>provided</scope>
        </dependency>
        <!--jstl坐标-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
            <scope>compile</scope>
        </dependency>
        <!--druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.15</version>
        </dependency>
        <!--spring相关坐标-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.5.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.5.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.5.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <!--AOP的实现包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <!--SpringMVC坐标-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <!--mybatis坐标-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <!--mybatis-plus依赖坐标-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.4.3.4</version>
        </dependency>
        <!--spring与mybatis对接依赖坐标-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.4</version>
        </dependency>
        <!--jackson-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.0</version>
        </dependency>
        <!--分页坐标-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.2</version>
        </dependency>
        <!--文件上传的依赖坐标-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
        <!-- redis依赖 -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.4.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.4.2</version>
        </dependency>
        <!--spring-security依赖-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <!-- tomcat7的插件, 不同tomcat版本这个也不一样 -->
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <!-- 通过maven tomcat7:run运行项目时,访问项目的端口号 -->
                    <port>80</port>
                    <!-- 项目访问路径  本例:localhost:9090,  如果配置的aa, 则访问路径为localhost:9090/aa-->
                    <path>/</path>
                    <uriEncoding>UTF-8</uriEncoding>
                </configuration>
            </plugin>
        </plugins>

        <resources>
            <resource>
                <directory>src/main/java</directory>
                <!-- 此配置不可缺,否则mybatis的Mapper.xml将会丢失 -->
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <!--指定资源的位置-->
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>*.xml</include>
                    <include>*.properties</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

添加完记得刷新一下,接下来在resource文件下添加jdbc.properties文件

添加SqlMapperConfig文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
        <!-- 是否开启全局缓存 -->
        <setting name="cacheEnabled" value="true" />
        <!-- 查询时,关闭关联对象即时加载以提高性能 -->
        <setting name="lazyLoadingEnabled" value="false"/>
        <!-- 对于未知的SQL查询,允许返回不同的结果集 -->
        <setting name="multipleResultSetsEnabled" value="true"/>
        <!-- 设置关联对象加载的形态 -->
        <setting name="aggressiveLazyLoading" value="true"/>
    </settings>
    <!--扫描使用注解的类所在的包-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="param1" value="value1"/>
        </plugin>
    </plugins>
    <mappers>
        <package name="com.jbz.mapper"/>
    </mappers>
</configuration>

添加spring-mvc.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:mvc="http://www.springframework.org/schema/mvc"
       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.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
    <!--1.配置组件扫描-->
    <context:component-scan base-package="com.jbz"/>
    <!--2.开启MVC注解驱动 自动加载三大组件和转换json-->
    <mvc:annotation-driven/>
    <!--3.配置视图解析器-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/pages/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--4.开放资源的访问css、js、img等-->
    <mvc:default-servlet-handler/>
    <!--5.配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--对哪些资源执行拦截操作-->
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/personal/login"/>
            <!--指定拦截器的处理类-->
            <bean class="com.jbz.interceptor.PermissionInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    <!--6.配置文件上传解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--文件上传总大小-->
        <property name="maxUploadSize" value="5242800"/>
        <!--文件上传的编码格式-->
        <property name="defaultEncoding" value="UTF-8"/>
        <!--单个文件上传的大小-->
        <property name="maxUploadSizePerFile" value="5242800"/>
    </bean>

</beans>

添加applicationContext.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:aop="http://www.springframework.org/schema/aop"
       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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--1.加载jdbc.properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源对象及相关参数-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--3.配置SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--设置数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--设置Mybatis核心配置文件 导入SqlMapConfig.xml-->
        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>
        <!--指定mybatis的mapper映射文件的位置-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>
    <!--4.配置Mapper扫描的包-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--配置指定mapper扫描接口的包路径-->
        <property name="basePackage" value="com.jbz.mapper"/>
    </bean>
    <!--5.开启动态代理-->
    <aop:aspectj-autoproxy/>
    <!--引入redis配置文件-->
    <import resource="application-redis.xml"/>
</beans>

添加redis.properties

添加application-redis.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"
       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.xsd">
    <!--配置redis-->
    <!--引入redis参数 -->
    <context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true"  />
    <!--设置数据池-->
    <bean id="poolConfig"  class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大空闲数 -->
        <property name="maxIdle" value="${redis.maxIdle}" />
        <!-- 最大空闲数 -->
        <property name="maxTotal" value="${redis.maxActive}" />
        <!-- 返回连接时,检测连接是否成功 -->
        <property name="testOnBorrow" value="${redis.testOnBorrow}" />
    </bean>
    <!--配置连接工厂-->
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!-- IP地址 -->
        <property name="hostName" value="${redis.host}" />
        <!-- 端口号 -->
        <property name="port" value="${redis.port}" />
        <property name="poolConfig" ref="poolConfig" />
    </bean>
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <!--针对各种数据选择序列化方式-->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <!--开启事务 -->
    <property name="enableTransactionSupport" value="true"/>
    </bean>
    <!-- 使用中间类解决RedisCache.jedisConnectionFactory的静态注入,从而使MyBatis实现第三方缓存 -->
    <bean id="redisCacheTransfer" class="com.jbz.utils.RedisCacheTransfer">
        <property name="jedisConnectionFactory" ref="connectionFactory"/>
    </bean>
    <!-- Redis缓存管理对象 -->
    <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg index="0" ref="redisTemplate" />
    </bean>
</beans>

redis整合所需要的工具类

package com.jbz.utils;

import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author: jbz
 * @date: 2023/1/10
 * @description:
 * @version: 1.0
 */
public class RedisCache implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
    private static JedisConnectionFactory jedisConnectionFactory;
    private final String id;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public RedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("require an ID");
        }
        logger.debug("RedisCache:id=" + id);
        this.id = id;
    }

    @Override
    public void clear() {
        RedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            connection.flushDb();
            connection.flushAll();
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }
    public String getId() {
        return this.id;
    }
    public Object getObject(Object key) {
        Object result = null;
        RedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
            result = serializer.deserialize(connection.get(serializer.serialize(key)));
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }
    public int getSize() {
        int result = 0;
        RedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            result = Integer.parseInt(connection.dbSize().toString());
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }
    public void putObject(Object key, Object value) {
        RedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
            connection.set(serializer.serialize(key), serializer.serialize(value));
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }
    public Object removeObject(Object key) {
        RedisConnection connection = null;
        Object result = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
            result = connection.expire(serializer.serialize(key), 0);
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }
    public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
        RedisCache.jedisConnectionFactory = jedisConnectionFactory;
    }
}
package com.jbz.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;

/**
 * @author: jbz
 * @date: 2023/1/10
 * @description:
 * @version: 1.0
 */
public class RedisCacheTransfer {

    @Autowired
    public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
        RedisCache.setJedisConnectionFactory(jedisConnectionFactory);
    }
}

 

 在web.xml文件中配置过滤器、前端控制器、监听器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--设置首页-->
    <welcome-file-list>
        <welcome-file>/pages/login.jsp</welcome-file>
    </welcome-file-list>
    <!--解决乱码的过滤器-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--配置前端控制器-->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:*.xml</param-value>
        </init-param>
        <!--
        配置springMVC什么时候启动。参数必须是整数,
        如果等于0或大于0,随着容器的启动而启动
        如果小于0在第一次请求进来的时候启动
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--在spring-mvc.xml加入<mvc:default-servlet-handler/>后再添加如下-->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.js</url-pattern>
        <url-pattern>*.css</url-pattern>
        <url-pattern>/img/*</url-pattern>
        <url-pattern>/plugins/*</url-pattern>
    </servlet-mapping>
    <!--配置监听器-->
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>


</web-app>

接下来将准备好的静态资源放在webapp下,至此项目搭建完毕 。

四、实现基本的CRUD操作

首先在main文件下新建如图所示的包:

首先在domain包下新建Product实体类,实体类的字段要与数据库product表中的字段名、类型要一致。注意:由于我们要用到redis,因此实体类必须要实现序列化接口。

package com.jbz.domain;

import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

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

/**
 * @author: jbz
 * @date: 2022/12/22
 * @description:
 * @version: 1.0
 */
public class Product implements Serializable {
    //编号
    private Integer id;
    //产品编号
    private String productNum;
    //产品名称
    private String productName;
    //出发城市名称
    private String cityName;
    //出发时间
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date departureTime;
    private String departureTimeStr;
    //产品价格
    private Double productPrice;
    //产品详情描述
    private String productDesc;
    //产品状态 0:下架 1:上架
    private Integer productStatus;
    private String productStatusStr;

    public Product() {
    }
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getProductNum() {
        return productNum;
    }

    public void setProductNum(String productNum) {
        this.productNum = productNum;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public Date getDepartureTime() {
        return departureTime;
    }

    public void setDepartureTime(Date departureTime) {
        this.departureTime = departureTime;
    }

    public String getDepartureTimeStr() {
        return departureTimeStr;
    }

    public void setDepartureTimeStr(String departureTimeStr) {
        this.departureTimeStr = departureTimeStr;
    }

    public Double getProductPrice() {
        return productPrice;
    }

    public void setProductPrice(Double productPrice) {
        this.productPrice = productPrice;
    }

    public String getProductDesc() {
        return productDesc;
    }

    public void setProductDesc(String productDesc) {
        this.productDesc = productDesc;
    }

    public Integer getProductStatus() {
        return productStatus;
    }

    public void setProductStatus(Integer productStatus) {
        this.productStatus = productStatus;
    }

    public String getProductStatusStr() {
        if (productStatus == 0) {
            productStatusStr = "下架";
        }else if(productStatus == 1){
            productStatusStr = "上架";
        }
        return productStatusStr;
    }

    public void setProductStatusStr(String productStatusStr) {
        this.productStatusStr = productStatusStr;
    }

    @Override
    public String toString() {
        return "Product{" +
                "id='" + id + '\'' +
                ", productNum='" + productNum + '\'' +
                ", productName='" + productName + '\'' +
                ", cityName='" + cityName + '\'' +
                ", departureTime=" + departureTime +
                ", departureTimeStr='" + departureTimeStr + '\'' +
                ", productPrice=" + productPrice +
                ", productDesc='" + productDesc + '\'' +
                ", productStatus=" + productStatus +
                ", productStatusStr='" + productStatusStr + '\'' +
                '}';
    }
}

 其次我们在数据持久层mapper包下新建ProductMapper接口,添加@Repoistory注解,用来操作数据库,封装一个方法用来条件查询所有的产品,在resources文件夹下新建mapper文件夹,新建ProductMapper.xml,文件名要与接口名一致。在namespace里通过全限定类名进行映射。select标签的id要与方法名一致,返回类型与方法名返回类型一致

 

 接着我们在service层新建对应的接口与实现类,在实现类中添加@Service注解,通过依赖注入,将mapper层注入到ioc容器,通过mybatis分页插件进行分页,调用方法返回PageInfo对象。

 在controller层新建ProductController,添加@Controller注解,和请求的一级路径@RequestMapping("/product"),将对应的service接口依赖注入进来,创建方法并添加二级路径@RequestMapping("/queryProductList"),具体代码如下:

    @RequestMapping("/queryProductList")
    public String queryProductList(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "4") int pageSize,
                                   @RequestParam(value = "productName", required = false) String productName, Model model) {
        //调用service的查询产品列表的方法
        PageInfo<Product> pageInfo = productService.queryAllProduct(pageNum, pageSize, productName);
        //设置数据 保存容器中
        model.addAttribute("pageInfo", pageInfo);
        //返回给视图
        return "product-list";
    }

方法的参数为前端页面进行分页和查询所需要的请求参数 ,将查询出的PageInfo对象添加到Model中,并返回给视图(这里的视图的前缀和后缀已经在配置文件中进行配置过了,实际的路径应该为/pages/product-list.jsp)。

接下来我们在静态页面product-list.jsp页面中,通过jstl表达式用foreach标签进行遍历出来,在对应的侧边栏产品管理上添加跳转url路径:

 

  <c:forEach items="${pageInfo.list}" var="product">
                                    <tr>
                                        <td><label><input name="ids" type="checkbox" value="${product.id}"></label></td>
                                        <td>${product.id}</td>
                                        <td>${product.productNum}</td>
                                        <td>${product.productName}</td>
                                        <td>${product.cityName}</td>
                                        <td><fmt:formatDate value="${product.departureTime}" pattern="yyyy-MM-dd"/></td>
                                        <td>${product.productPrice}</td>
                                        <td>${product.productDesc}</td>
                                        <td>${product.productStatusStr}</td>
                                        <td class="text-center">
                                            <button type="button" class="btn bg-olive btn-xs"
                                                    onclick="location.href='${pageContext.request.contextPath}/product/queryProductById?id='+${product.id}">
                                                编辑
                                            </button>
                                            <button type="button" class="btn bg-olive btn-xs"
                                                    onclick="delProduct(${product.id})">删除
                                            </button>
                                        </td>
                                    </tr>
                                </c:forEach>

最后运行项目,在点击控制台的链接,点击侧边栏的产品管理至如下图所示:

 增删改的操作与上面类似,这里就不展开说了。

五、SpringAOP实现记录日志

我们在AOP包下,新建LogAspect切面类,添加注解@Aspect声明当前类是一个切面类,添加@Pointcut注解配置通用切入点表达式。我们选择用环绕通知来进行日志的记录。环绕通知的特点是目标执行前后,都进行增强(控制目标方法执行),它的应用场景:日志、缓存、权限、性能监控、事务管理。具体代码如下所示。接着我们需要在applciationContext.xml文件中添加<aop:aspectj-autoproxy/>开启动态代理,当用户在页面发起请求时,该切面类会获取到当前用户的请求路径,请求的方法名。同时我们在此基础上获取当前用户的用户名,访问时间以及当前用户的ip地址,将这些数据封装到Syslog实体类中。最后在将Syslog实体类保存到数据库中。

 

package com.jbz.aop;

import com.jbz.constant.MessageConstant;
import com.jbz.domain.User;
import com.jbz.utils.RecordingLogUtils;
import com.jbz.utils.SetDataUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;

/**
 * @author: jbz
 * @date: 2023/1/7
 * @description: 记录日志的切面类(对用户的请求进行日志监控与记录)
 * @version: 1.0
 */
@Component
@Aspect
public class LogAspect {
    //依赖注入
    @Autowired
    private RecordingLogUtils recordingLogUtils;
    @Autowired
    private SetDataUtils setDataUtils;

    //定义方法执行开始时间
    private long startTime;
    //定义请求路径url
    private StringBuilder url;
    //获取请求方法名
    private String methodStr;
    //定义ServletRequestAttributes对象
    private static ServletRequestAttributes requestAttributes;


    //通用切入点表达式
    @Pointcut("execution (* com.jbz.controller.*.*(..))")
    private void pt1() {

    }

    //环绕通知
    @Around("pt1()")
    public Object aroundLog(ProceedingJoinPoint proceedingJoinPoint) {
        requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        //调用初始化方法
        init(proceedingJoinPoint);
        //设置返回值
        Object obj = null;
        //获取方法执行所需的参数
        Object[] objs = proceedingJoinPoint.getArgs();
        //获取当前用户的username
        String userName = getUserName(objs[0].toString());
        try {
            //执行切入点的方法
            obj = proceedingJoinPoint.proceed(objs);
        } catch (Throwable e) {
            setDataUtils.setExceptionCount(requestAttributes.getRequest().getSession());
            recordingLogUtils.insertSysLog(userName, MessageConstant.EXECUTE_METHOD_EXCEPTION, url.toString(), 0, methodStr);
            e.printStackTrace();
        } finally {
            //定义方法执行结束时间
            long endTime = System.currentTimeMillis();
            recordingLogUtils.insertSysLog(userName, MessageConstant.EXECUTE_METHOD_SUCCESS, url.toString(), (int) (endTime - startTime), methodStr);
        }
        return obj;
    }

    /**
     * @author: jbz
     * @description: 获得当前用户名
     * @date: 2023/1/7 19:00
     * @param: username
     * @return: String
     */
    public String getUserName(String username) {
        //获取Session对象
        HttpSession session = requestAttributes.getRequest().getSession();
        //获取当前用户
        User user = (User) session.getAttribute("user");
        //判断当前用户是否为null,为null说明未登录
        if (user == null) {
            //在未登录情况下,用户进行操作会调用拦截器进行登录,此时将参数username返回
            return username;
        } else {
            //不为null,说明当前用户处于登录状态,就将用户的username返回
            return user.getUsername();
        }
    }

    /**
     * @author: jbz
     * @description: 初始化参数
     * @date: 2023/1/7 19:39
     * @param: proceedingJoinPoint
     * @return: void
     */
    public void init(ProceedingJoinPoint proceedingJoinPoint) {
        //获取HttpSession对象
        HttpSession session = requestAttributes.getRequest().getSession();
        setDataUtils.setVisitCount(session);
        //给方法开始时间进行赋值
        startTime = System.currentTimeMillis();
        url = new StringBuilder();
        //获取执行方法的类
        Object target = proceedingJoinPoint.getTarget();
        RequestMapping[] classRequest = target.getClass().getAnnotationsByType(RequestMapping.class);
        //获取请求方法的一级路径
        url.append(classRequest[0].value()[0]);
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        //获取请求方法
        Method method = signature.getMethod();
        //获取请求方法名
        methodStr = "[类名]" + target.getClass().getName() + "[方法名]" + method.getName();
        //获取请求方法的二级路径
        RequestMapping[] methodRequest = method.getAnnotationsByType(RequestMapping.class);
        url.append(methodRequest[0].value()[0]);
        if(url.toString().equals("orders/add")){
            setDataUtils.setOrderCount(session);
        }
    }


}

六、SpringMVC拦截器interceptor拦截用户的权限

SpringMVC的拦截器类似于Servlet开发中的过滤器Filter,用于处理器进行预处理和后处理。将拦截器按一定的顺序联结成一条链,这条链称为拦截器链。在访问被拦截的方法或字段时,拦截器链的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。拦截器和过滤器的区别如图所示:

拦截器的步骤:

  1. 创建拦截器类实现HandlerInterceptor接口
  2. 配置拦截器
  3. 测试拦截器效果

首先我们先封装一个权限处理的工具类PermissionHandleUtils.java,该工具类用于判断请求的路径是否包含在用户所具有的权限中,并返回boolean值,随后我们创建PermissionInterceptor.java实现HanlerInterceptor接口,重写preHandle(),preHandle方法是在目标方法之前执行,是预处理。随后我们在spring-mvc文件中对拦截器进行配置,配置如下图所示。在拦截器中我们首先对用户的登录进行权限控制,用户在没有登录的情况下,不能对后台菜单进行访问操作,当用户点击菜单时,跳转至登录页面,只有在用户登录成功安置后才能进行后台功能的操作。当用户在登录状态时,通过权限处理的工具类对当前请求进行权限判断,只有满足要求,才能放行,否则就跳转至错误页面。在权限拦截的同时,还要添加日志到数据库中。

 

PermissionHandleUtils.java

package com.jbz.utils;

import com.jbz.domain.User;
import com.jbz.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;

/**
 * @author: jbz
 * @date: 2023/1/9
 * @description:
 * @version: 1.0
 */
@Component
public class PermissionHandleUtils {
    //依赖注入
    @Autowired
    private IUserService userService;

    public Boolean judgePermissions(HttpServletRequest request,User user) {
        //获取用户的具有权限请求路径
        String[] permissionUrl = userService.queryUserPermissionsById(user.getId());
        //获得当前请求路径
        String requestURL = request.getRequestURI();
        //进行判断
        if (permissionUrl != null && permissionUrl.length != 0) {
            List<String> list = Arrays.asList(permissionUrl);
            if (list.contains("/*")) {
                return true;
            } else if (list.contains(requestURL)) {
                return true;
            } else if (requestURL.equals("/favicon.ico")) {
                return true;
            } else return list.contains("/personal/*") && requestURL.contains("/personal/");
        } else {
            return false;
        }
    }
}

 PermissionInterceptor.java

package com.jbz.interceptor;

import com.jbz.constant.MessageConstant;
import com.jbz.domain.User;
import com.jbz.utils.PermissionHandleUtils;
import com.jbz.utils.RecordingLogUtils;
import com.jbz.utils.SetDataUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * @author: jbz
 * @date: 2023/1/9
 * @description: 用户权限拦截器(对用户的请求进行权限的监控与拦截)
 * @version: 1.0
 */
public class PermissionInterceptor implements HandlerInterceptor {
    @Autowired
    private PermissionHandleUtils permissionHandle;
    @Autowired
    private RecordingLogUtils recordingLog;
    @Autowired
    private SetDataUtils setDataUtil;
    //在目标方法之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断用户是否登录
        HttpSession session = request.getSession();
        //从session中获取用户
        User user = (User) session.getAttribute("user");
        //如果用户为null或者用户名为空
        if (user == null || "".equals(user.getUsername())) {
            //还停留在登录页
            response.sendRedirect(request.getContextPath() + "/pages/login.jsp");
            return false;
        } else {
            Boolean flag = permissionHandle.judgePermissions(request, user);
            if(flag){
                //放行
                return true;
            }else {
                setDataUtil.setPermissionCount(request.getSession());
                //添加日志
                recordingLog.insertSysLog(user.getUsername(), MessageConstant.EXECUTE_METHOD_FAIL,request.getRequestURI(),0,"方法已拦截");
                //跳转至错误页面
                response.sendRedirect(request.getContextPath() + "/pages/403.jsp");
                return false;
            }
        }
    }
}

七、Bcrypt加密算法

首先我们需要导入spring-security的依赖,如下所示,接着封装一个工具类用于返回一个BCryptPasswordEncoder并注册进IOC容器

 <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>5.0.5.RELEASE</version>
 </dependency>
@Component
public class MyPasswordEncoder {

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(10);
    }
}

接着我们在servic业务层对应的实现类里依赖注入PasswordEncoder,在新增用户以及修改密码的时候,对密码进行加密,使用passwordEncoder.encode()对传入的密码进行加密,返回加密后的密码,将user对象的密码修改为加密后的密码,最后存入数据库中。在登录时进行密码解密与比对,同一个密码的加密结果也是不一样的,PasswordEncoder也有加盐处理,passwordEncoder.matches(password,encodePassword)对密码进行比对,返回boolean值。代码如下所示。

  @Override
    public void updatePersonalUser(User user) {
        //对密码进行加密处理
        String encodePassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodePassword);
        userMapper.updateUser(user);
    }
 @Override
    public void addUser(User user, int[] roleIds) {
        //对密码进行加密处理
        String encodePassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodePassword);
        userMapper.insertUser(user);
        userRoleMapper.insertUserRole(user.getId(), roleIds);
    }
  @Override
    public User login(String username, String password) {
        //调用userMapper的根据用户名查user的方法
        User user = userMapper.queryUserByUsername(username);
        //判断user是否为null
        if (user != null) {
            //判断密码是否一致
            if (passwordEncoder.matches(password,user.getPassword())) {
                return user;
            }
        }
        return null;
    }

八、总结

项目源码地址:GitHub - JBZ0805/ssm

项目运行效果如下图所示,

 

 

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘子味衬衫.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值