SSM 尚筹网 Vue3 + Vite + Java

SSM 尚筹网

2022年3月27日15:07:51

1.Maven结构:

在这里插入图片描述

1.基于Maven的MyBatis逆向工程

1.逆向工程为 common-reverse

2.配置Maven(pom.xml)

pom.xml

   <dependencies>
        <!--mybatis核心依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
    </dependencies>
    <build>
        <!--构建过程中用到的插件-->
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.0</version>
<!--                <configuration>-->
<!--                    <configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>-->
<!--                    <overwrite>true</overwrite>-->
<!--                    <verbose>true</verbose>-->
<!--                </configuration>-->
                <configuration>
                    <configurationFile>
                    <!-- 这里是配置generatorConfig.xml的路径,这里空着不写表示默认在resources目录下找generatorConfig.xml文件 -->
                    </configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
                <!--插件的依赖-->
                <dependencies>
                    <!--逆向工程核心依赖-->
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.4.0</version>
                    </dependency>
                    <!--数据库连接池-->
                    <dependency>
                        <groupId>com.alibaba</groupId>
                        <artifactId>druid</artifactId>
                        <version>1.2.8</version>
                    </dependency>
                    <!--mysql驱动-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.22</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>7</source>
                    <target>7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

3.在Resources目录下新建文件 generatorConfig.xml

generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!-- mybatis-generator:generate -->
    <!-- context 是逆向工程的主要配置信息 -->
    <!-- id:配置名称 -->
    <!-- targetRuntime:设置生成的文件的 mybatis 版本 -->
    <context id="msqlTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!--  是否去除自动生成的注释 true:是;false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection
                driverClass="com.mysql.cj.jdbc.Driver"
                connectionURL="jdbc:mysql://localhost:3306/ssm_crowd?serverTimezone=UTC"
                userId="root"
                password="123456">
        </jdbcConnection>
        <!--  默认 false,把 JDBC DECIMAL  和 NUMERIC  类型解析为 Integer,为 true 时把
        JDBC DECIMAL
        和 NUMERIC  类型解析为 java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>
        <!-- targetProject:生成 Entity 类的路径 -->
        <javaModelGenerator targetProject=".\src\main\java"
                            targetPackage="xyz.xiaozaiyi.crowd.entity">
            <!-- enableSubPackages:是否让 schema 作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
            <!--  从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- targetProject:XxxMapper.xml 映射文件生成的路径 -->
        <sqlMapGenerator targetProject=".\src\main\java"
                         targetPackage="xyz.xiaozaiyi.crowd.mapper">
            <!-- enableSubPackages:是否让 schema 作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>
        <!-- targetPackage:Mapper 接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetProject=".\src\main\java"
                             targetPackage="xyz.xiaozaiyi.crowd.mapper">
            <!-- enableSubPackages:是否让 schema 作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>
        <!--  数据库表名字和我们的 entity 类对应的映射指定 -->
        <table tableName="t_admin" domainObjectName="Admin" />
    </context>
</generatorConfiguration>

4.开始构建

在这里插入图片描述

在这里插入图片描述

2.资源归为

把逆向工程生成的文件分别移动或者复制到如下位置

3. 父工程管理

版本声明

<properties>
    <!-- 声明属性, 对 Spring 的版本进行统一管理 -->
    <!-- spring.version是别名,随便起,但要跟dependences标签中一致 -->
    <spring.version>5.2.11.RELEASE</spring.version>
    <!-- 声明属性, 对 SpringSecurity 的版本进行统一管理 -->
    <spring.security.version>5.4.2</spring.security.version>
</properties>
<dependencyManagement>
    <dependencies>
        <!-- Spring 依赖 -->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
            <scope>runtime</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

        <!--数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>

        <!--mybatis核心依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>

        <!-- MyBatis 与 Spring 整合 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.5</version>
        </dependency>
        <!-- MyBatis 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>
        <!-- 日志 -->
           <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.3.0-alpha5</version>
            <scope>test</scope>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <!-- Spring5 可以不用加 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>
        <!-- 其他日志框架的中间转换包 -->
        <!-- Spring5 可以不用加 -->
        <!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.slf4j/jul-to-slf4j -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jul-to-slf4j</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>
        
        <!-- Spring 进行 JSON 数据转换依赖 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.11.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.3</version>
        </dependency>
        <!--  其他  (servlet,junit)-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
        <!-- SpringSecurity 对 Web 应用进行权限管理 -->
        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>5.4.2</version>
        </dependency>
        <!-- SpringSecurity 配置 -->
        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.4.2</version>
        </dependency>
        <!-- SpringSecurity 标签库 -->
        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-taglibs -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>5.4.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

4.SSM整合步骤

  1. 在子工程加入相应依赖

  2. 配置 jdbc.properties

  3. 创建Spring 配置文件 applicationContext.xml

    1. 加载 jdbc.properties属性文件
    2. 配置数据源 (并且测试)
    3. 配置SqlSessionFactoryBean整合Mybatis
      • 指定mybatis全局配置文件位置
      • 配置Mybatis的Mapper配置文件位置
      • 装配数据源
    4. 配置mapperScannerConfigurer来扫描Mapper所在的包
    4.1日志系统

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.3.0-alpha5</version>
    <scope>test</scope>
</dependency>

加入 logback 依赖即可.如果需要配置在类路径下创建 logback.xml(名字指定)

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置 -->
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是: 时间、 日志级别、 线程名称、 打印日志的类、 日志主体内容、 换行 -->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n
            </pattern>
        </encoder>
    </appender>
    <!-- 设置全局日志级别。 日志级别按顺序分别是: DEBUG、 INFO、 WARN、 ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="INFO">
        <!-- 指定打印日志的 appender, 这里通过“STDOUT”引用了前面配置的 appender -->
        <appender-ref ref="STDOUT"/>
    </root>
    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="xiaozaiyi.crowd.mapper" level="DEBUG"/>
</configuration>
4.2 声明式事务

基于xml配置文件的方式配置事务管理 spring-mybatis-tx.xml

步骤:

  1. 创建Spring专门管理事务的配置文件
  2. 配置自动扫描包 (service 层)
<!--    1.配置扫描包  主要是将service 的包加载到ioc容器中
 use-default-filters属性:
	true:默认值,把 @Service,@Controller和@Repository 的注解的类都进行扫描
-->
<context:component-scan base-package="xiaozaiyi.crowd.service.imp" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
  1. 事物控制配置
    1. 配置控制数据源
  2. 配置事物切面
 <!--3.配置事物切面-->
    <aop:config>
        <aop:pointcut id="txPoint" expression="execution(* xiaozaiyi.crowd.server..*.*(..))"/>
        <!-- 3.1配置事物增强-->
        <!--将切入点 表达式和事务通知关联起来-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
    </aop:config>
  1. 配置事务通知
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <!--查询事物方法: 只读属性 (可以优化代码)-->
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="count*" read-only="true"/>
            <!--增删改方法:配置事务传播行为、回滚异常-->
            <!--propagation属性:
                    REQUIRED:默认值,表示当前方法必须工作在事务中,如果当前线程上没有已经开启的事务,则自己开新事务。
                            如果已经有则使用自己的.
                            顾虑:用别人的事务有可能被”回滚。
                    REQUIRES_NEW : 建议使用。表示当前方法必须工作在事务中,如果当前线程上没有已经开启的事务,
                            则自己开新事务。就算是已经有了,也在自己开启的事务中运行。(不受别的影响)
                             好处:不受其他事务回滚的影响
               rollback-for属性:
                    默认值 : RuntimeException
                    建议 : 异常就回滚
            -->
            <tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
            <tx:method name="update*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
            <tx:method name="remove*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
            <tx:method name="batch*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
        </tx:attributes>
    </tx:advice>
  1. 配置表述层 web.xml

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">

    <!-- 1.启动spring容器-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- 2.SpringMVC的前端控制器,拦截所有请求-->
    <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:springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--    3.字符编码过滤器,一定放在所有过滤器之前-->
    <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>
        <init-param>
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

2.功能开发

1.管理员登录

Java提供了MessageDigest md5加密接口

String algorithm = "md5";
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
1.1 异常映射
  1. 首先在web.xml配置DispatcherServlet异常控制器
 <!-- 2.SpringMVC的前端控制器,拦截所有请求-->
<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:springmvc.xml</param-value>
    </init-param>
    <init-param>
        <param-name>throwExceptionIfNoHandlerFound</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
  1. 创建一个异常类 CustomException.java
import lombok.Data;
/**
 * @author : Crazy_August
 * @description :
 * @Time: 2022-03-31   22:18
 */
@Data
public class CustomException extends RuntimeException {
    static final long serialVersionUID = -7034897190745766939L;

    private Integer code;

    public CustomException() {
        super();
    }

    public CustomException(String message) {
        super(message);
    }

    public CustomException(Integer code, String message) {
        super(message);
        this.code = code;
    }
	省略一些继承父类方法...
}
  1. 异常映射 @ControllerAdvice
package xiaozaiyi.crowd.controller;

import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.NoHandlerFoundException;
import xiaozaiyi.crowd.exception.CustomException;
import xiaozaiyi.crowd.util.ResultEntity;

/**
 * @author : Crazy_August
 * @description :
 * @Time: 2022-03-31   21:25
 */
@ControllerAdvice
public class RestExceptionController {

    //400错误
    @ExceptionHandler({HttpMessageNotReadableException.class, MissingServletRequestParameterException.class})
    @ResponseBody
    public ResultEntity requestNotReadable() {
        return ResultEntity.error(400, "数据类型不匹配");
    }

    //自定义异常
    @ExceptionHandler({CustomException.class})
    @ResponseBody
    public ResultEntity loginFailed(CustomException e) {
        return ResultEntity.error(e.getCode(), e.getMessage());
    }

    /**
     * 捕获404异常
     * @return
     */
    @ExceptionHandler({NoHandlerFoundException.class})
    @ResponseBody
    public ResultEntity NoResourceException() {
        System.out.println("NoResourceException");
        return ResultEntity.error(404, "请求地址不存在");
    }

    @ExceptionHandler({Exception.class})
    @ResponseBody
    public ResultEntity server500() {
        return ResultEntity.error(500, "未知错误");
    }
}

哪有那么多的才子佳人,哪有那么多的山盟海誓,哪有那么多的王子公主,哪有那么多的良辰美景,花前月下

2.用户管理

数据库表

CREATE TABLE t_role(
	id INT NOT NULL auto_increment COMMENT '主键',
	`name` VARCHAR(100) NOT NULL COMMENT '角色名称',
	PRIMARY KEY(id)
)

同用户管理

3.菜单维护

数据库表

CREATE TABLE	if not EXISTS t_menu(
	`id` INT NOT NULL auto_increment,
	`pid` INT NULL DEFAULT NULL,
	`name` VARCHAR(255) NOT NULL,
	`router` VARCHAR(255),
	PRIMARY KEY (`id`)
	);

INSERT INTO `t_menu` VALUES (1, NULL, '系统权限菜单', NULL);
INSERT INTO `t_menu` VALUES (2, 1, '控制面板', '/admin/main');
INSERT INTO `t_menu` VALUES (3, 1, '权限管理', NULL);
INSERT INTO `t_menu` VALUES (4, 3, '用户维护', '/admin/main/user');
INSERT INTO `t_menu` VALUES (5, 3, '角色维护', '/admin/main/role');
INSERT INTO `t_menu` VALUES (6, 3, '菜单维护', '/admin/main/permission');
INSERT INTO `t_menu` VALUES (7, 1, '业务审核', NULL);
INSERT INTO `t_menu` VALUES (8, 7, '实名认证审核', '/admin/main/auth-cert');
INSERT INTO `t_menu` VALUES (9, 7, '广告审核', '/admin/main/auth-adv');
INSERT INTO `t_menu` VALUES (10, 7, '项目审核', '/admin/main/auth-project');
INSERT INTO `t_menu` VALUES (11, 1, '业务管理', NULL);
INSERT INTO `t_menu` VALUES (12, 11, '资质维护', '/admin/main/cert');
INSERT INTO `t_menu` VALUES (13, 11, '分类管理', '/admin/main/type');
INSERT INTO `t_menu` VALUES (14, 11, '流程管理', '/admin/main/process');
INSERT INTO `t_menu` VALUES (15, 11, '广告管理', '/admin/main/advertisement');
INSERT INTO `t_menu` VALUES (16, 11, '消息模板', '/admin/main/message');
INSERT INTO `t_menu` VALUES (17, 11, '项目分类', '/admin/main/project-type');
INSERT INTO `t_menu` VALUES (18, 11, '项目标签', '/admin/main/tag');
INSERT INTO `t_menu` VALUES (19, 1, '参数管理', '/admin/main/param');
  1. 菜单的难点

    多层级,需要用到树的概念

在数据库的表示方法

在java代码中对查询到的数据进行组装

Menu.java

@Data
public class Menu {
    private Integer id;

    private Integer pid;

    private String name;

    private String router;
    // 存放子节点
    private List<Menu> children;

    public List<Menu> getChildren() {
       	// 如果为空就创建一个list,为了防止空指针异常
        if (children == null) {
            children = new ArrayList<Menu>();
        }
        return children;
    }
}

2.通过两种方式进行数据组装

方式一: 暴力解法,时间复杂度 o(n2)

@Override
public Menu getAllMenu() {
    // 先过去数据库查到的数据
    List<Menu> menusList = menuMapper.selectByExample(null);
    // 1.用来存储一个根节点
    Menu rootMenu = null;
    // 2.遍历所有的菜单
    for (Menu item : menusList) {
        // 3.获取pid属性
        Integer pid = item.getPid();
        //4.检测是否为null
        if (pid == null) {
            // 5.如果是null,说明是根节点
            rootMenu = item;
            continue;
        }
        // 6.如果不是null,说明不是根节点,说明是子节点,可以直接添加到父节点的children集合中
        // 7.再次遍历所有的菜单,找到子节点的父节点
        for (Menu maybeFather : menusList) {
            // 8.获取父节点的id
            Integer maybeFatherId = maybeFather.getId();
            // 9.检测父节点的id是否和子节点的pid相等
            if (Objects.equals(maybeFatherId,pid)) {
                // 10.如果相等,说明找到了父节点,将子节点添加到父节点的children集合中
                maybeFather.getChildren().add(item);
                break;
            }
        }
    }
    // 11.返回根节点
    return rootMenu;
}

方式二 : 利用Map进行改进

/**
* 方式二
* @return 返回根节点
 */
@Override
public Menu getAllMenus() {
    List<Menu> menuList = menuMapper.selectByExample(null);
    // 1.用来存储一个根节点
    Menu rootMenu = null;
    // 创建一个集合,用来存储 id 和 Menu 对象对应的关系便于查找父节点
    Map<Integer, Menu> menuMap = new HashMap<>();

    // 2.遍历所有的菜单
    for (Menu menu : menuList) {
        // 3.获取id属性
        Integer id = menu.getId();
        menuMap.put(id, menu);
    }
    
    //再次遍历给父节点填充子元素
    for (Menu menu : menuList) {
        // 4.获取子节点的pid属性
        Integer pid = menu.getPid();
        // 5.检测是否为null
        if (pid == null) {
            // 6.如果是null,说明是根节点
            rootMenu = menu;
            continue;
        }
        // 7. 如果不是null,说明不是根节点,说明是子节点,可以直接添加到父节点的children集合中
        Integer childrenPid = menu.getPid();
        Menu pidMenu = menuMap.get(childrenPid);
        if (pidMenu == null) {
            pidMenu = new Menu();
        }
        // 8. 将子节点添加到父节点的children集合中
        pidMenu.getChildren().add(menu);
    }
    return rootMenu;
}

4.角色分配

思路: 需要中间表来建立用户和角色身份关系

CREATE IF NOT EXISTS TABLE t_inner_admin_role(
	id INT NOT NULL auto_increment,
	`admin_id` INT,
	`role_id`	INT,
	PRIMARY KEY(id)
	);

小总结: 在使用 element-plus的穿梭框右边的数据要绑定到 v-model

git 命令 ,清除缓存 暂存区 git rm -r --cached .

加载配置文件可以加入多个

<init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath:springmvc.xml
        classpath:spring-mybatis-tx.xml
    </param-value>
</init-param>

5.权限维护

  1. SQL: t_auth.sql
-- Table structure for t_auth
CREATE TABLE IF NOT EXISTS t_auth(
 `id` INT NOT NULL auto_increment,
 `name` VARCHAR(200),
 `label` VARCHAR(200),
 `category_id` INT,
 PRIMARY KEY(id)
 )

 -- ----------------------------
-- Records of t_auth
-- ----------------------------
INSERT INTO `t_auth` VALUES (Null, Null, '用户模块', NULL);
INSERT INTO `t_auth` VALUES (Null, 'user:get', '查询', 1);
INSERT INTO `t_auth` VALUES (Null, 'user:add', '新增', 1);
INSERT INTO `t_auth` VALUES (Null, 'user:update', '更新', 1);
INSERT INTO `t_auth` VALUES (Null, 'user:delete', '删除', 1);
INSERT INTO `t_auth` VALUES (Null, Null, '角色模块', NULL);
INSERT INTO `t_auth` VALUES (Null, 'role:get', '查询', 6);
INSERT INTO `t_auth` VALUES (Null, 'role:add', '新增', 6);
INSERT INTO `t_auth` VALUES (Null, 'role:update', '更新', 6);
INSERT INTO `t_auth` VALUES (Null, 'role:delete', '删除', 6);
INSERT INTO `t_auth` VALUES (11, Null, '权限维护模块', NULL);
INSERT INTO `t_auth` VALUES (Null, 'auth:get', '查询', 11);
INSERT INTO `t_auth` VALUES (Null, 'auth:add', '新增', 11);
INSERT INTO `t_auth` VALUES (Null, 'auth:update', '更新', 11);
INSERT INTO `t_auth` VALUES (Null, 'auth:delete', '删除', 11);

6.分配权限

1.关联角色和权限的中间表 t_inner_role_auth.sql

CREATE TABLE IF NOT EXISTS t_inner_role_auth(
id INT NOT NULL auto_increment,
`role_id` INT,
`auth_id` INT,
PRIMARY KEY(id)
);

Tag:

map 转为 list

List<Integer> result = map.keySet().stream()
                .collect(Collectors.toList());
result.forEach(System.out::println);
       
List<String> result2 = map.values().stream()
                .collect(Collectors.toList());
result2.forEach(System.out::println);
        System.out.println("\n3. Export Map Value to List..., say no to banana");
List<String> result3 = map.keySet().stream()
                .filter(x -> !"banana".equalsIgnoreCase(x))
                .collect(Collectors.toList());
result3.forEach(System.out::println);

3.权限认证 springSecurity

Security /sɪˈkjʊərəti / 安全

类似产品:Shiro

3.1 spring security 简介

spring security 的核心功能主要包括:

  • 认证 (你是谁)
  • 授权 (你能干什么)
  • 攻击防护 (防止伪造身份)

工作流程图:

  1. 导入jar包
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring.security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>${spring.security.version}</version>
</dependency>
  1. 配置 web.xml

重点:

<!--SpringSecurity核心过滤器链-->
<!--springSecurityFilterChain名词不能修改-->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
  1. 创建基于注解的java配置类 此类需要基础 webSecurityConfigurationAdapter
@Configuration
//启动web环境下权限控制功能
@EnableWebSecurity
public class WebAppSecurityConfig  extends WebSecurityConfigurerAdapter {

}

启动报错如下:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' available

在这里插入图片描述
在这里插入图片描述

启动流程图:

**tig:**三大组件的启动顺序:

  1. ContextLoaderListener 初始化,创建Spring 的IOC容器
  2. DelegatingFilterProxy 初始化,查找IOC容器,查找Bean
  3. DispatcherServlet 初始化,创建SpringMVC的IOC容器

解决方案一 : 两个IOC容器合二为一, 不使用ContextLoadderListener,让DispatcherServlet加载所有Spring配置文件

解决方案二:改源码
修改DelegatingFilterProxy的源码,修改两处:

  1. 初始化时直接跳过查找IOC容器的环节,

  2. 第一次请求的时候直接找SpringMVC的 IOC容器

在这里插入图片描述

org.springframework.web.filte —> DelegatingFilterProxy.java

 @Override
    protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // If no target bean name specified, use filter name.
                if (this.targetBeanName == null) {
                    this.targetBeanName = getFilterName();
                }
                // Fetch Spring root application context and initialize the delegate 
                // 此处对源码进行了修改
                WebApplicationContext wac = findWebApplicationContext();
//				if (wac != null) {
//					this.delegate = initDelegate(wac);
//				}
                // 此处对源码进行了修改结束
            }
        }
    }

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    // Lazily initialize the delegate if necessary.
    Filter delegateToUse = this.delegate;
    if (delegateToUse == null) {
        synchronized (this.delegateMonitor) {
            delegateToUse = this.delegate;
            if (delegateToUse == null) {
                // 此处对源码进行了修改
                //把原来查找IOC容器代码注释掉
                //WebApplicationContext wac = findWebApplicationContext();

   //按我们自己的需要重新编写
   //1.获取ServletContext对象
   ServletContext sc = this.getServletContext();

   //2.拼接 SpringMVC 将IOC容器存入 ServletContext 域的时候使用的属性名,web.xml所配置的名称
   String servletName = "DispatcherServlet";

    String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + servletName;

    //3.根据attrName从获取ServletContext域中获取IOC容器对象
    WebApplicationContext wac = (WebApplicationContext) sc.getAttribute(attrName);
  	// 此处对源码进行了修改结束
                if (wac == null) {
                    throw new IllegalStateException("No WebApplicat... fo");
                }
                delegateToUse = initDelegate(wac);
            }
            this.delegate = delegateToUse;
        }
    }
}

3.2引入Redis

   <!-- redis-->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.6.3</version>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.1</version>
</dependency>

一定一定要版本对应问题!!!,查问题一个下午…

3.3登录功能重做

在这里插入图片描述

概念速查:

Authentication 接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager 接口:定义了认证Authentication的方法

UserDetailsService 接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

UserDetails 接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

3.3.1 思路分析

登录

①自定义登录接口

调用 ProviderManager 的方法进行认证 如果认证通过生成 jwt

把用户信息存入redis中

②自定义UserDetailsService

在这个实现类中去查询数据库

校验:

①定义Jwt认证过滤器

获取token

解析token获取其中的userid

从redis中获取用户信息

存入SecurityContextHolder

配置过滤器

tig: spring中在过滤器异常无法被全局异常捕获 @ControllerAdvice ExceptionHandler({xxx.class})

解决方案:

在 过滤器中 捕获异常,抛到controller

try {
    if (Objects.isNull(loginUser)) {
        // 携带信息
        throw new CustomException(200, CrowdConstant.NO_LOGIN_USER);
    }
	....
} catch (Exception e) {
    request.setAttribute("exception", e);
    request.getRequestDispatcher("/server/filter").forward(request, response);
}

在Controller中获取:

/**
     * 处理在过滤器中抛出的异常 
     * @return
*/
@RequestMapping("/server/filter")
public ResultEntity filterRefuse(HttpServletRequest request) {
    CustomException errorMessage = (CustomException)request.getAttribute("exception");
    throw new CustomException(errorMessage.getCode(), errorMessage.getMessage());
}
3.3.2 密码加密
  1. 创建 BCryptPasswordEncoder bean容器
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
  1. 装配

    @Autowired
    private PasswordEncoder bCryptPasswordEncoder;
    
  2. 使用 bCryptPasswordEncoder.encode()

3.4 权限控制
  1. 在SpringSecurity中,会使用默认的 FilterSecurityInterceptor 来进行权限校验。在 FilterSecurityInterceptor 中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。

    所以我们在项目中只需要把当前登录用户的权限信息也存入 Authentication。

    然后设置我们的资源所需要的权限即可。

  2. 使用

    • 先开启相关配置。在配置类上加入注解@EnableGlobalMethodSecurity(prePostEnabled = true)
    • 然后就可以使用对应的注解。@PreAuthorize,基于方法上的注解
@RequestMapping("update")
@PreAuthorize("hasAuthority('user:update')")
public ResultEntity updateAdmin(@RequestBody Admin admin) {
    boolean success = adminService.updateAdmin(admin);
    if (!success) {
        return ResultEntity.success(100, CustomConstant.UPDATE_FAILED);
    }
    return ResultEntity.success(200, CustomConstant.UPDATE_SUCCESS);
}

在这里插入图片描述

拥有对应的权限才可以访问方法.

在这里插入图片描述

3.其他注解

表达式中可以使用 # 号 ,比如:
在表达式中, 可以使用 #user 来代表方法中的参数 user

@PreAuthorize ("#user.name == authentication.principal.username")
public void deleteUser(User user){}
  • @PreFilter() 集合类型参数的方法 ,@PreFilter(value = “filterObject%2 == 0”),filterObject就是每个item
  • @preFilter:在方法执行前对传入的参数进行过滤。只能对集合类型的数据进行过滤。
    @PostFilter:在方法执行后对方法返回值进行规律。只能对集合类型的数据进行过滤。

4. 会员系统模块

1.项目设计

1.架构图

在这里插入图片描述

2.需要创建的工程

  • 父工程、聚合工程: crowd-parent (pom)

  • 注册中心: crowd-eureka 1000

  • 实体类模块: crowd-entity

  • mysql数据服务: crowd-mysql-provider 2000

  • redis数据服务 : crowd-redis-provider 3000

  • 会员中心: crowd-authentication-consumer 5000

  • 项目维护 : crowd-project-consumer 6000

  • 订单维护 :crowd-order-consumer 7000

  • 支付功能 :crowd-pay-consumer 8000

  • 网关: crowd-zuul 80

    • 使用 getaway代替 80
  • 接口: crowd-service-api 4000

3.配置相关依赖

1.crowd-parent --> pom.xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR8</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.6.7</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.9</version>
        </dependency>
    </dependencies>
</dependencyManagement>
2.注册中心: crowd-eureka --> pom.xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

2.1 配置eureka的启动类

@EnableEurekaServer
@SpringBootApplication
public class EurekaMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMainApplication.class, args);
    }
}

2.2 配置 yaml 文件

server:
  port: 1000

spring:
  application:
    name: august-eureka
eureka:
  instance:
    hostname: localhost
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
    fetch-registry: false # #是否从eureka中拉取注册信息
    register-with-eureka: false # 不需要在自己的注册中心注册

**启动报错:**Error creating bean with name ‘configurationPropertiesBeans’ defined in class path resource

解决方案: 修改父工程的pom.xml文件,把spring-boot-dependencies的版本改为 2.3.12.RELEASE

<dependency>
    <!-- Import dependency management from Spring Boot -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.12.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

启动服务成功,访问 localhost:1000

在这里插入图片描述

3.实体类模块: crowd-entity

Tig:

VO

View Object 视图对象

用途: 1.接收前端发来的数据

​ 2.把数据发回浏览器

PO

Persisttent Object 持久化对象

用途:1.将数据封装到PO对象存入数据库

​ 2.将数据库的数据查询存入PO对象

一个PO对象对应数据库的一张表

DO

Data Object 数据对象

用途:1.从redis查询得到的数据封装为DO对象

​ 2.从ElasticSearch查询得到的数据封装为DO对象

​ 3.从Solr 查询到的数据封装为DO对象…

DTO

Data Transfer Object 数据传输对象

用途:1. 从Consumer 发送数据到 Provider

  1. Provider返回数据到Consumer
4.mysql数据服务: crowd-mysql-provider
<dependencies>
    <!-- eureka 客户服务-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>
  1. 配置 yml 文件
server:
  port: 2000
spring:
  application:
    name: august-mysql
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: 123456
      url: jdbc:mysql://localhost:3306/ssm_crowd?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka
mybatis-plus:
  configuration:
    log-prefix: t_
logging:
  level:
    xiaozaiyi.crowd.mapper: debug
    xiaozaiyi.crowd.test: debug

3.编写启动类:

报错: Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}

检查自己的配置:

eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka

解决方案: 检查defaultZone 是不是拼写错误.

在启动出现: Completed shut down of DiscoveryClient,服务启动完成马上停止

解决方案: 说明缺少web依赖,在pom.xml文件加入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

4.编写sql,以及在entity中编写对应PO:

-- ----------------------------
-- Table structure for t_member
-- ----------------------------
CREATE TABLE `t_member` (
  `id` int NOT NULL AUTO_INCREMENT,
  `login_acct` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_password` char(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `auth_status` int DEFAULT NULL COMMENT '实名认证状态0 - 未实名认证, 1 - 实名认证申\r\n请中, 2 - 已实名认证',
  `user_type` int DEFAULT NULL COMMENT ' 0 - 个人, 1 - 企业',
  `real_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `card_num` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `acct_type` int DEFAULT NULL COMMENT '0 - 企业, 1 - 个体, 2 - 个人, 3 - 政府',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
5.redis数据服务 : crowd-redis-provider
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- eureka 客户服务-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

配置文件:

server:
  port: 3000

spring:
  application:
    name: august-redis
  redis:
    host: localhost
    port: 6379
    database: 0
    
eureka:
  client:
    service-url:
      defaultZo
6.接口:crowd-service-api

1.pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- eureka 客户服务-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
  1. application.yml
server:
  port: 4000
spring:
  application:
    name: august-api
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka
logging:
  level:
    # feign日志以什么级别监控哪个接口
    xiaozaiyi.crowd*: debug

3.启动类

@SpringBootApplication(exclude= DataSourceAutoConfiguration.class)
//要加入此注解表示开启feign
@EnableFeignClients
public class ServiceApiMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceApiMainApplication.class, args);
    }
}

4.编写Feign接口,调用远程provider

/**
 * 调用远程 redis provider
 */
// 注册中心的微服务名称,也可以使用 url 表示
@FeignClient("august-redis")
//浏览器请求到达这里
public interface IRedisClient {
    @RequestMapping("set/redis/key/value")
    R<String> setRedisKeyValue(@RequestParam("key") String key, @RequestParam("value") String value);
    @RequestMapping("set/redis/key/value/timeout")
    R<String> setRedisKeyValueWithTimeout(
            @RequestParam("key") String key,
            @RequestParam("value") String value,
            @RequestParam("time") long time,
            @RequestParam("timeUnit") TimeUnit timeUnit
    );
    @RequestMapping("get/redis/value/by/key")
    R<String> getRedisValueByKey(@RequestParam("key") String key);
    @RequestMapping("remove/redis/value/by/key")
    R<String> removeRedisValueByKey(@RequestParam("key") String key);
}

5.编写provider对应的Controller

@RestController
public class RedisController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    //路径要和Feign保存一致,并且参数注解不能省略
    @RequestMapping("set/redis/key/value")
    R<String> setRedisKeyValue(@RequestParam("key") String key, @RequestParam("value") String value) {
        try {
            ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
            operations.set(key, value);
            return R.status(true);
        } catch (Exception e) {
            return R.fail(e.getMessage());
        }
    }
    ......
7.会员中心: crowd-authentication-consumer

配置同上

8.网关: crowd-zuul

1.pom.xml

<dependencies>
  		<!-- eureka 客户服务-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

</dependencies>

2.配置类

server:
  port: 80
spring:
  application:
    name: august-gateway
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka
zuul:
  ignored-services: "*"
  sensitive-headers: "*"
  routes:
    portal:
      service-id: august-authentication
      path: /**

4. 会员代码编写

// 0. 把 VO 转为 PO
MemberPO memberPO = new MemberPO();
BeanUtils.copyProperties(memberVO,memberPO);

1.windos 查看端口进程

netstat -ano |findstr "4000"
taskkill -f -pid 6680

2.zuul网关改为gateway

原因: 由于zuul网关作出的全局异常处理解决一天还是不能解决,菜鸡留下没有技术的眼泪,后改用gateway来代替zuul…

gateway 有大量的过滤器来自定义.

首先导入gateway依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

注意: 依赖中发现的springMvc与gateway不能兼容,需要删除spring-boot-starter-web相关引用

server:
  port: 80

spring:
  application:
    name: august-gateway
  main:
    allow-bean-definition-overriding: true
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 启动 discovery 网关集成,可以实现服务发现
      routes:
       - id: member
         uri: lb://august-authentication
         predicates:
           - Path=/member/**
         filters:
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka

exclude:
  auth:
    #不需要授权验证的请求地址,可设置多个,使用逗号分隔开,会跳过AuthFilter授权验证
    path: /member/register,/member/get/phone/code,/member/login,/error

1.配置跨域

创建配置类: CorsConfig.java

@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

2.统一的格式的json数据返回到前端

我们只需要重写 DefaultErrorAttributes类的部分方法即可.

在这里插入图片描述

这是默认实现:

在这里插入图片描述

我们需要建立一个类继承 DefaultErrorAttributes 重写一下 public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options)方法

注意: status是必须返回的,因为后面的代码需要调用,判断状态,所以这个字段是必须.为了防止空指针异常

在这里插入图片描述

代码:

/**
 * @author : Crazy_August
 * @description :
 * @Time: 2022-04-27   20:50
 */
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    private final Logger logger = LoggerFactory.getLogger(MyErrorAttributes.class);
    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = new HashMap<>();
        try {
            CustomException error = (CustomException) getError(request);
            String message = error.getMessage();
            Integer code = error.getCode();
            errorAttributes.put("message", message);
            errorAttributes.put("status", code);
            errorAttributes.put("success", true);
        } catch (Exception e) {
            logger.error(e.getMessage());
            errorAttributes.put("status", 500);
            errorAttributes.put("message", e.getMessage());
        }
        return errorAttributes;
    }
}

2.方案二:

在这里插入图片描述

全球org.springframework.web.server基本。WebExceptionHandler,呈现ErrorAttributes,异常处理的接口

代码:

/**
 * @author : Crazy_August
 * @description :
 * @Time: 2022-04-27   22:30
 */
public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {

    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                 ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }


    /**
     * 获取异常属性
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        int code = 500;
        Throwable error = super.getError(request);
        if (error instanceof NotFoundException) {
            code = 404;
        }
        if (error instanceof ResponseStatusException) {
            code = ((ResponseStatusException) error).getStatus().value();
        }

        return response(code, this.buildMessage(request, error));
    }


    /**
     * 指定响应处理方法为JSON处理的方法
     *
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }
    
    /**
     * 构建异常信息
     *
     * @param request
     * @param ex
     * @return
     */
    private String buildMessage(ServerRequest request, Throwable ex) {
        StringBuilder message = new StringBuilder("Failed to handle request [");
        message.append(request.methodName());
        message.append(" ");
        message.append(request.uri());
        message.append("]");
        if (ex != null) {
            message.append(": ");
            message.append(ex.getMessage());
        }
        return message.toString();
    }


    /**
     * 构建返回的JSON数据格式
     *
     * @param status  状态码
     * @param message 信息
     * @return
     */
    public static Map<String, Object> response(int status, String message) {
        Map<String, Object> map = new HashMap<>(16);
        map.put("code", status);
        map.put("message", message);
        map.put("data", null);
        map.put("status", status);
        return map;
    }
}

同样,map.put("status", status)不能省.

第二步创建配置类:

/**
 * 异常处理配置类
 */
@Configuration
@AutoConfigureBefore(ErrorWebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {

   private final ServerProperties serverProperties;

   private final ApplicationContext applicationContext;

   private final ResourceProperties resourceProperties;

   private final List<ViewResolver> viewResolvers;

   private final ServerCodecConfigurer serverCodecConfigurer;

   public ErrorHandlerConfiguration(ServerProperties serverProperties,
                            ResourceProperties resourceProperties,
                            ObjectProvider<List<ViewResolver>> viewResolversProvider,
                            ServerCodecConfigurer serverCodecConfigurer,
                            ApplicationContext applicationContext) {
      this.serverProperties = serverProperties;
      this.applicationContext = applicationContext;
      this.resourceProperties = resourceProperties;
      this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
      this.serverCodecConfigurer = serverCodecConfigurer;
   }

   @Bean
   @Order(Ordered.HIGHEST_PRECEDENCE)
   public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
      MyErrorWebExceptionHandler exceptionHandler = new MyErrorWebExceptionHandler(
         errorAttributes,
         this.resourceProperties,
         this.serverProperties.getError(),
         this.applicationContext);
      exceptionHandler.setViewResolvers(this.viewResolvers);
      exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
      exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
      return exceptionHandler;
   }
}

3.登录验证

crowd-gateway —> SecurityFilter.java

在此过滤器认证时候带token,并且redis上存有信息

/**
 * 身份认证过滤
 *
 * @author : Crazy_August
 * @description :
 * @Time: 2022-04-27   19:40
 */
@Component
public class SecurityFilter implements GlobalFilter, Ordered {
    private final Logger log = LoggerFactory.getLogger(SecurityFilter.class);
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 读取配置文件中排除不需要授权的URL
     */
    @Value("${exclude.auth.path}")
    private String excludeAuthUrl;

    private AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.对于排除的 url 放行
        String path = exchange.getRequest().getURI().getPath();
        log.info(path);
        log.info(excludeAuthUrl);

        if (!StringUtils.isEmpty(excludeAuthUrl)) {
            String[] excludePaths = excludeAuthUrl.split(",");
            // 在排除的url放行
            for (String pattern : excludePaths) {
                if (pathMatcher.match(pattern, path)) {
                    return chain.filter(exchange);
                }
            }
        }
        // 需要过滤的请求
        boolean authorization = exchange.getRequest().getHeaders().containsKey("authorization");
        if (!authorization) {
            throw new CustomException(HttpStatus.SC_OK, CustomConstant.NULL_TOKEN);
        }
        List<String> authorizationList = exchange.getRequest().getHeaders().get("authorization");

        String token = authorizationList.get(0);

        if (!StringUtils.hasText(token)) {
            //"token为空,禁止访问!"
            throw new RuntimeException(CustomConstant.ERROR_TOKEN);
        }
        // 如果带了 Bearer 并且是第一次访问
        if (token.startsWith("Bearer null")) {
            //"token格式不正确,禁止访问!"
            throw new CustomException(HttpStatus.SC_OK, CustomConstant.ERROR_TOKEN);
        }

        String[] split = token.split(" ");
        // 解析 token
        String Id;
        try {
            Claims claims = JwtUtil.parseJWT(split[1]);
            Id = claims.getSubject();
        } catch (Exception e) {
            throw new CustomException(HttpStatus.SC_OK, CustomConstant.ERROR_TOKEN);
        }
        // 从redis中获取用户信息
        String redisId = CustomConstant.REDIS_PREFIX + Id;

        // 获取 得到用户信息
        String accessTokenById = stringRedisTemplate.opsForValue().get(redisId);

        if (StringUtils.isEmpty(accessTokenById)) {
            throw new CustomException(HttpStatus.SC_OK, CustomConstant.NO_LOGIN_USER);
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

4.登录退出

1.Authentication-consumer的controller接收请求

@GetMapping("/logout")
public R<MemberVO> memberLogout(HttpServletRequest request) {
    String authorizationToken = request.getHeader("authorization");
    String[] split = authorizationToken.split(" ");
    String token  = split[1];
    R<MemberVO> memberVOR  =  memberService.memberLogout(token);
    return R.status(memberVOR.isSuccess(), memberVOR.getMessage());
}

2.删除redis的缓存信息

@Override
public R<MemberVO> memberLogout(String token) {
    String userId = null;
    try {
        // 获取 jwt 解析出用户 id;
        Claims claims = JwtUtil.parseJWT(token);
        userId = claims.getSubject();
    } catch (Exception e) {
        R.fail(CustomConstant.IDENTITY_IS_OVERDUE);
    }
    String redisValueByUserId = CustomConstant.REDIS_PREFIX + userId;
    // 删除 redis 信息
    R<String> stringR = iRedisClientFeign.removeRedisValueByKey(redisValueByUserId);
    boolean success = stringR.isSuccess();
    if (!success){
        return R.fail(CustomConstant.LOGOUT_ERROR);
    }
    return R.success(CustomConstant.LOGOUT_SUCCESS);
}

5.验证码发送

1.手机验证码发送

依赖:

dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>alibabacloud-dysmsapi20170525</artifactId>
    <version>1.0.1</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.15</version>
    <scope>compile</scope>
</dependency>
    /**
     * 发送验证码
     *
     * @param phoneNumbers 手机号
     * @param code         验证码
     * @return 响应字符串
     */
    public static String sendSmS(String phoneNumbers, String code) {
        Properties properties = new Properties();
        properties.getProperty("configuration.properties");
        InputStream resourceAsStream = SendUtil.class.getClassLoader().getResourceAsStream("configuration.properties");
        // 解决中文乱码问题
        BufferedReader bf = null;
        if (resourceAsStream != null) {
            bf = new BufferedReader(new InputStreamReader(resourceAsStream));
        }
        try {
            properties.load(bf);
            String accessKeyId = properties.getProperty("aliyun.sms.accessKeyId");
            String accessKeySecret = properties.getProperty("aliyun.sms.accessKeySecret");
            String signName = properties.getProperty("aliyun.sms.signName");
            String templateCode = properties.getProperty("aliyun.sms.templateCode");

            // 配置 Credentials 认证信息,包括 ak, secret, token
            StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
                    .accessKeyId(accessKeyId)
                    .accessKeySecret(accessKeySecret)
                    .build());

            // 配置产品 Client
            AsyncClient client = AsyncClient.builder()
                    .region("undefined") // 产品服务区域 ID
                    .credentialsProvider(provider)
                    .overrideConfiguration(
                            ClientOverrideConfiguration.create()
                                    .setEndpointOverride("dysmsapi.aliyuncs.com")
                    )
                    .build();
            String phoneCode = "{'code':" + code + "}";
            SendSmsRequest sendSmsRequest = SendSmsRequest.builder()
                    .signName(signName)
                    .templateCode(templateCode)
                    .phoneNumbers(phoneNumbers)
                    .templateParam(phoneCode)
                    .build();

            // 异步获取接口请求返回值
            CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);

            // 同步阻塞获取返回值方式
            SendSmsResponse resp = null;
            try {
                resp = response.get();
                SendSmsResponseBody body = resp.getBody();
                String jsonBody = new Gson().toJson(body);
                return jsonBody;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            } finally {
                // Finally, close the client
                client.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                bf.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

2.QQ邮件发送

依赖:

<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.6.2</version>
</dependency>
 public static String sendEmail() throws MessagingException, GeneralSecurityException {
        Properties properties = new Properties();
        MailSSLSocketFactory sf = new MailSSLSocketFactory();
        sf.setTrustAllHosts(true);
        properties.put("mail.transport.protocol", "smtp"); // 连接协议
        properties.put("mail.smtp.host", "smtp.qq.com"); // 主机名
        properties.put("mail.smtp.port", 465); // 端口号
        properties.put("mail.smtp.socketFactory.port", 465);

        properties.put("mail.smtp.auth", true);
        properties.put("mail.smtp.ssl.enable", true); // 设置是否使用ssl安全连接,一般都使用
        properties.put("mail.smtp.ssl.socketFactory", sf);
        properties.put("mail.debug", true); // 设置是否显示debug信息,true会在控制台显示相关信息
        properties.put("mail.user", "193**922@qq.com");
        properties.put("mail.password", "tne***dkcaejbe"); //开启pop3/smtp时的验证码

        // 得到回话对象
        Session session = Session.getDefaultInstance(properties, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(
                        properties.getProperty("mail.user"),
                        properties.getProperty("mail.password")); //发件人邮件用户名、授权码;
            }
        });
        session.setDebug(true);//代表启用debug模式,可以在控制台输出smtp协议应答的过程
        // 获取邮件对象
        Message message = new MimeMessage(session);

        // 设置发件人邮箱地址
        message.setFrom(new InternetAddress("19**94922@qq.com"));

        // 设置收件人邮箱地址
        message.setRecipient(Message.RecipientType.TO, new InternetAddress("2027485027@qq.com")); // 一个收件人
// message.setRecipients(Message.RecipientType.TO, new InternetAddress[]{new InternetAddress("xxx@qq.com"), new InternetAddress("xxx@qq.com"), new InternetAddress("xxx@qq.com")}); // 多个收件人

        // 设置邮件标题
        message.setSubject("这是一封测试邮件");
        // 设置邮件内容
        message.setText("这是测试邮件的正文");

        Transport.send(message);

        return null;
    }

报错: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)或者 could not connect to host “smtp.qq.com”, port: 465, response: -1

看看是不是导入依赖问题

正确依赖:

<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.6.2</version>
</dependency>

错误依赖:

<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4.7</version>
</dependency>

查了半天…

6.OSS腾讯云上传

1.创建账号等过程省略…,主要是获取到 secretId 、 secretKey

2.导入依赖

<!-- 腾讯云服务-->
<dependency>
    <groupId>com.qcloud</groupId>
    <artifactId>cos_api</artifactId>
    <version>5.6.75</version>
</dependency>

3.配置文件 configuration.properties

qcloud.secretId=AKIDJxxxxiiTxByYsw
qcloud.secretKey=oLxxxxL7
qcloud.bucket.name=auguxxxx2
qcloud.oss.area=ap-chengdu
qcloud.oss.url=https://axx922.c

4.工具类

package xiaozaiyi.crowd.util;

import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import lombok.Data;
import xiaozaiyi.crowd.exception.CustomException;

import java.io.*;
import java.util.*;

/**
 * 腾讯云 oss 存储
 *
 * @author : Crazy_August
 * @description :
 * @Time: 2022-04-30   15:04
 */
@Data
public class QOssUploadUtil {

    private static String url;

    private static String bucketName;

    private static String secretId;

    private static String secretKey;

    private static String OSSArea;

    private static COSClient cosClient;

    static {
        Map<String, String> configuration = configuration();
        if (configuration != null) {
            url = configuration.get("url");
            bucketName = configuration.get("bucketName");
            secretId = configuration.get("secretId");
            secretKey = configuration.get("secretKey");
            OSSArea = configuration.get("OSSArea");
        }

    }

    /**
     *  上传文件
     * 
     * @param file 上传的本地文件路径
     * @return 上传后的文件访问路径
     */
    public static String uploadResource(String file) {

        COSClient cosClient = getCOSClient(secretId, secretKey, OSSArea);
        Calendar cal = Calendar.getInstance();
        int month = cal.get(Calendar.MONTH) + 1;
        int day = cal.get(Calendar.DAY_OF_MONTH);
        String folderName = month + "-" + day;
        String fileMainName = UUID.randomUUID().toString().replace("-", "");
        // 从原始文件名中获取文件扩展名
        String fileExtension = file.substring(file.lastIndexOf("."));
        // 文件名
        String fileName = folderName + "/" + fileMainName + fileExtension;
        // 文件路径
        // 指定要上传的文件
        File localFile = new File(file);

        // 指定文件上传到 COS 上的路径,即对象键。例如对象键为 folder/picture.jpg,则表示将文件 picture.jpg 上传到 folder 路径下
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, localFile);

        PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);

        String eTag = putObjectResult.getETag();
        if (eTag == null) {
            throw new CustomException(100, "上传失败");
        }
        cosClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
        // 返回文件的访问地址
        return url + "/" + fileName;
    }
    
    /**
     * 下载文件到本地
     * 
     * @param key 文件名
     * @param outputFilePath  下载到本地的路径
     */
    public static void downloadResource(String key, String outputFilePath) {

        COSClient cosClient = getCOSClient(secretId, secretKey, OSSArea);

        GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);

        COSObject cosObject = cosClient.getObject(getObjectRequest);
        COSObjectInputStream cosObjectInput = cosObject.getObjectContent();
        // 关闭输入流
        try {
            cosObjectInput.close();
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException(100, "下载失败");
        }
        String outputPath = outputFilePath + File.separator + key.substring(key.indexOf("/") + 1);
        File downFile = new File(outputPath);
        getObjectRequest = new GetObjectRequest(bucketName, key);
        ObjectMetadata downObjectMeta = cosClient.getObject(getObjectRequest, downFile);
        String eTag = downObjectMeta.getETag();
        if (eTag == null) {
            throw new CustomException(100, "下载失败");
        }
    }

    /**
     * 创建桶
     *
     * @param bucketName
     */
    public static void createBucket(String bucketName) {
        COSClient cosClient = getCOSClient(secretId, secretKey, "");
        // 创建存储空间
        CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
        // 设置 bucket 的权限为 Private(私有读写)、其他可选有 PublicRead(公有读私有写)、PublicReadWrite(公有读写)
        createBucketRequest.setCannedAcl(CannedAccessControlList.Private);
        try {
            Bucket bucketResult = cosClient.createBucket(createBucketRequest);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException(100, "创建存储空间失败");
        }
    }

    /**
     * 删除桶
     *
     * @param bucketName
     */
    public static void removeBucket(String bucketName) {
        COSClient cosClient = getCOSClient(secretId, secretKey, OSSArea);
        try {
            cosClient.listBuckets().forEach(System.out::println);
            cosClient.deleteBucket(bucketName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException(100, "删除创建存储失败");
        }
    }

    /**
     * 删除文件
     *
     * @param fileName
     * @return
     */
    public static void removeResource(String fileName) {
        COSClient cosClient = getCOSClient(secretId, secretKey, OSSArea);
        try {
            cosClient.deleteObject(bucketName, fileName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException(100, "删除文件失败");
        }
    }

    /**
     * 读取配置文件
     *
     * @return 返回配置文件 Map
     */
    private static Map<String, String> configuration() {
        Map<String, String> configProps = new HashMap<>();
        Properties properties = new Properties();
        InputStream resourceAsStream = QOssUploadUtil.class.getClassLoader().getResourceAsStream("configuration.properties");
        // 解决中文乱码问题
        BufferedReader bf = null;
        if (resourceAsStream != null) {
            bf = new BufferedReader(new InputStreamReader(resourceAsStream));
        }
        try {
            properties.load(resourceAsStream);
            String secretId = properties.getProperty("qcloud.secretId");
            String secretKey = properties.getProperty("qcloud.secretKey");
            // 指定文件将要存放的存储桶
            String bucketName = properties.getProperty("qcloud.bucket.name");
            // 设置 bucket 的地域,
            String OSSArea = properties.getProperty("qcloud.oss.area");
            String url = properties.getProperty("qcloud.oss.url");
            configProps.put("url", url);
            configProps.put("secretId", secretId);
            configProps.put("secretKey", secretKey);
            configProps.put("bucketName", bucketName);
            configProps.put("OSSArea", OSSArea);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (bf != null) {
                    bf.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                assert resourceAsStream != null;
                resourceAsStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return configProps;
    }
    /**
     * 返回 COSClient
     *
     * @return COSClient
     */
    private static COSClient getCOSClient(String secretId, String secretKey, String OSSArea) {
        COSClient cosClient;
        try {
            // 1 初始化用户身份信息(secretId, secretKey)。
            COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
            // 2 设置 bucket 的地域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
            Region region = new Region(OSSArea);
            ClientConfig clientConfig = new ClientConfig(region);
            // 这里建议设置使用 https 协议
            clientConfig.setHttpProtocol(HttpProtocol.https);
            // 设置OSSClient允许打开的最大HTTP连接数,默认为1024个。
            clientConfig.setMaxConnectionsCount(1024);
            // 设置Socket层传输数据的超时时间,默认为50000毫秒。
            clientConfig.setSocketTimeout(50000);
            // 设置建立连接的超时时间,默认为50000毫秒。
            clientConfig.setConnectionTimeout(50000);
            // 设置从连接池中获取连接的超时时间(单位:毫秒),默认不超时。
            clientConfig.setConnectionRequestTimeout(1000);
            // 3 生成 cos 客户端。
            cosClient = new COSClient(cred, clientConfig);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException(100, "获取cos客户端失败");
        }
        return cosClient;
    }
}

7.发起项目模块

主要编写 crowd-project-consumer 这个微服务

1.建立sql

-- ----------------------------
-- Table structure for t_project_item_pic
-- ----------------------------
CREATE TABLE `t_project_item_pic` (
  `id` int NOT NULL AUTO_INCREMENT,
  `project_id` int DEFAULT NULL COMMENT '项目id',
  `item_pic_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '图片路径',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
-- ----------------------------
-- Table structure for t_project_tag
-- ----------------------------
CREATE TABLE `t_project_tag` (
  `id` int NOT NULL AUTO_INCREMENT,
  `project_id` int DEFAULT NULL COMMENT '项目id',
  `tag_id` int DEFAULT NULL COMMENT '标签id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

-- ----------------------------
-- Table structure for t_project_type
-- ----------------------------
DROP TABLE IF EXISTS `t_project_type`;
CREATE TABLE `t_project_type`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `projectid` int(11) NULL DEFAULT NULL,
  `typeid` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
)

-- ----------------------------
-- Table structure for t_return
-- ----------------------------
DROP TABLE IF EXISTS `t_return`;
CREATE TABLE `t_return`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `projectid` int(11) NULL DEFAULT NULL,
  `type` int(4) NULL DEFAULT NULL COMMENT '0 - 实物回报, 1 虚拟物品回报',
  `supportmoney` int(11) NULL DEFAULT NULL COMMENT '支持金额',
  `content` varchar(255) NULL DEFAULT NULL COMMENT '回报内容',
  `count` int(11) NULL DEFAULT NULL COMMENT '回报产品限额,“0”为不限回报数量',
  `signalpurchase` int(11) NULL DEFAULT NULL COMMENT '是否设置单笔限购',
  `purchase` int(11) NULL DEFAULT NULL COMMENT '具体限购数量',
  `freight` int(11) NULL DEFAULT NULL COMMENT '运费,“0”为包邮',
  `invoice` int(4) NULL DEFAULT NULL COMMENT '0 - 不开发票, 1 - 开发票',
  `returndate` int(11) NULL DEFAULT NULL COMMENT '项目结束后多少天向支持者发送回报',
  `describ_pic_path` varchar(255) NULL DEFAULT NULL COMMENT '说明图片路径',
  PRIMARY KEY (`id`) USING BTREE
)

CREATE TABLE `t_tag`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pid` int(11) NULL DEFAULT NULL,
  `name` varchar(255)  NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
)

DROP TABLE IF EXISTS `t_type`;
CREATE TABLE `t_type`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255)  NULL DEFAULT NULL COMMENT '分类名称',
  `remark` varchar(255)  NULL DEFAULT NULL COMMENT '分类介绍',
  PRIMARY KEY (`id`)
) 

2.编写 java 实体类和VO类

在这里插入图片描述

3.操作流程图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.详细代码见工程

部分功能截图

  1. 项目发起

在这里插入图片描述

  1. 回报设置

在这里插入图片描述

  1. 确认信息
    在这里插入图片描述

  2. 提交结果

在这里插入图片描述

8.前台显示

创建VO

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProjectTypeVO implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer id;
    private String name;
    private String remark;
    private List<PortalProjectVo> portalProjectVoList;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PortalProjectVo implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer projectId;
    private String projectName;
    // 项目头图
    private String headerPicturePath;
    // 发布时间
    private String deployDate;
    // 项目进度
    private Integer completion;
    // 项目支持人数
    private Integer supporter;
    // 项目支持金额
    private BigDecimal money;
}

获取数据的sql

<mapper namespace="xiaozaiyi.crowd.mapper.ProjectMapper">
    <resultMap id="selectProjectTypeVOListResultMap" type="xiaozaiyi.crowd.vo.ProjectTypeVO">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="remark" column="remark"/>
        <!-- List包含的属性
        property 是属性名称
        column 通过id查询的时候 portalProjectVoList
        ofType 是list里属性的类型
        select 是查询的sql方法的 id (接口加全类名)
        -->
        <collection property="portalProjectVoList" ofType="xiaozaiyi.crowd.vo.PortalProjectVo"
 column="id"select="xiaozaiyi.crowd.mapper.ProjectMapper.selectProjectProjectVOList"
        />
    </resultMap>
    <!--    List<ProjectTypeVO> selectProjectTypeVOList();-->
    <select id="selectProjectTypeVOList" resultMap="selectProjectTypeVOListResultMap">
        select id, `name`, remark
        from t_type
    </select>
    <select id="selectProjectProjectVOList" resultType="xiaozaiyi.crowd.vo.PortalProjectVo">
        SELECT tp.id projectId,
               project_name projectName,
               header_picture_path headerPicturePath,
               deploy_date deployDate,
               `completion` completion,
               supporter supporter,
               money money
        FROM t_project tp
                 LEFT JOIN t_project_type tpt
                           ON tp.id = tpt.project_id
        WHERE tpt.type_id = #{id}
        ORDER BY tp.id DESC LIMIT 0,4
    </select>
</mapper>

在这里插入图片描述

9.详情页面

建立VO

DetailProjectVO.java

package xiaozaiyi.crowd.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.util.List;

/**
 * @author : Crazy_August
 * @description : 项目详情信息
 * @Time: 2022-05-07   19:30
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DetailProjectVO {
    private Integer id;
    private String projectName;
    private String projectDesc;
    private String followerNum;
    private Integer status;
    private String money;
    private BigDecimal supportMoney;
    // 百分比
    private String percentage;
    private String deployDate;
    // 众筹天数
    private Integer day;
    private Integer lastDate;
    private Integer supportNum;
    private String headerPicturePath;
    private List<String> detailPicturePath;
    private List<DetailReturnVO> detailReturnVOList;
}

DetailReturnVO.java

package xiaozaiyi.crowd.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.math.BigDecimal;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class DetailReturnVO implements Serializable {
   
   private static final long serialVersionUID = 1L;

   private Integer id;
   
   // 支持金额
   private BigDecimal supportMoney;
   
   // 回报内容介绍
   private String content;
   
   // 总回报数量,0为不限制
   private Integer count;
   
   // 是否限制单笔购买数量,0 表示不限购,1表示限购
   private Integer signalPurchase;
   
   // 如果单笔限购,那么具体的限购数量
   private Integer purchase;
   
   // 运费,“0”为包邮
   private Integer freight;

   // 众筹结束后返还回报物品天数
   private Integer returnDate;
}

10.接入支付宝支付

网站:https://open.alipay.com/

1.使用沙箱环境

获取基本信息,配置密钥

在这里插入图片描述

2.java配置

aliyun:
  pay:
    # 支付宝公钥
    alipay-public-key: xxx
    app-id: 2021000119691212
    charset: utf-8
    # 支付宝网关
    gateway-url: https://openapi.alipaydev.com/gateway.do
    # 商户私钥,您的PKCS8格式RSA2私钥
    merchant-private-key: xxxx
    notify-url: http://你的url/notify
    # 页面跳转同步通知页面路径
    return-url: http://你的url/return
    # 签名方式
    sign-type: RSA2
    # 支付宝网关
    log-path: "D:\\"
    format: "json"

配置类:

AliPayResourceConfig.java

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "aliyun.pay")
public class AliPayResourceConfig {
    private String appId;
    // 商户私钥,您的PKCS8格式RSA2私钥
    private String merchantPrivateKey;
    // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
    private String alipayPublicKey;

    // 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
// public static String notifyUrl = "http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp";
    private String notifyUrl;
    //    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问00000
//    public static String returnUrl = "http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/return_url.jsp";
    private String returnUrl;
    // 签名方式
//    public static String sign_type = "RSA2";
    private String signType;
    // 字符编码格式
//    public static String charset = "utf-8";
    private String charset;
    // 支付宝网关
//    public static String gateWayUrl = "https://openapi.alipay.com/gateway.do";
    private String gatewayUrl;
    // 日志路径
    private String logPath;
    private String format;
}

PayController.java

@RestController
public class PayController {
    @Autowired
    private AliPayResourceConfig aliPayResourceConfig;
    @RequestMapping("/")
    public String pay() throws AlipayApiException {
        String gatewayUrl = aliPayResourceConfig.getGatewayUrl();
        String appId = aliPayResourceConfig.getAppId();
        String privateKey = aliPayResourceConfig.getMerchantPrivateKey();
        String alipayPublicKey = aliPayResourceConfig.getAlipayPublicKey();
        String format = aliPayResourceConfig.getFormat();
        String charset = aliPayResourceConfig.getCharset();
        String signType = aliPayResourceConfig.getSignType();
        String notifyUrl = aliPayResourceConfig.getNotifyUrl();
        String returnUrl = aliPayResourceConfig.getReturnUrl();
        //获得初始化的AlipayClient 实例
        DefaultAlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl, appId,
                privateKey, format, charset, alipayPublicKey, signType);
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        alipayRequest.setNotifyUrl(notifyUrl);
        alipayRequest.setReturnUrl(returnUrl);
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", "20213211321301001");
        bizContent.put("total_amount", 5000);
        bizContent.put("subject", "Iphone6 16G");
        bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
        alipayRequest.setBizContent(bizContent.toJSONString());
        AlipayTradePagePayResponse alipayTradePagePayResponse = alipayClient.pageExecute(alipayRequest);
        System.out.println(alipayTradePagePayResponse.getBody());
        return alipayTradePagePayResponse.getBody();
    }
}

11.订单模块

在这里插入图片描述

1.订单SQL

-- ----------------------------
-- Table structure for t_order
-- ----------------------------
CREATE TABLE `t_order`(
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `order_num` VARCHAR(100)  NULL DEFAULT NULL COMMENT '订单号',
  `pay_order_num` VARCHAR(100) NULL DEFAULT NULL COMMENT '支付宝流水号',
  `order_amount` double(10, 5) NULL DEFAULT NULL COMMENT '订单金额',
  `invoice` int(11) NULL DEFAULT NULL COMMENT '是否开发票(0 不开,1 开)',
  `invoice_title` VARCHAR(100) NULL DEFAULT NULL COMMENT '发票抬头',
  `order_remark` VARCHAR(100) NULL DEFAULT NULL COMMENT '订单备注',
  `address_id` VARCHAR(100)  NULL DEFAULT NULL COMMENT '收货地址 id',
  PRIMARY KEY (`id`)
) 


-- ----------------------------
-- Table structure for t_order_project
-- ----------------------------
CREATE TABLE `t_project` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `project_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
  `project_description` varchar(255) DEFAULT NULL COMMENT '项目描述',
  `money` bigint DEFAULT NULL COMMENT '筹集金额',
  `day` int DEFAULT NULL COMMENT '筹集天数',
  `status` int DEFAULT NULL COMMENT '0-即将开始,1-众筹中,2-众筹成功,3-众筹失败\r\n',
  `deploy_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '项目发起时间',
  `support_money` bigint DEFAULT NULL COMMENT '已筹集到的金额',
  `supporter` int DEFAULT NULL COMMENT '支持人数',
  `completion` int DEFAULT NULL COMMENT '百分比完成度',
  `member_id` int DEFAULT NULL COMMENT '发起人的会员id',
  `create_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '项目创建时间',
  `follower` int DEFAULT NULL COMMENT '关注人数',
  `header_picture_path` varchar(255) DEFAULT NULL COMMENT '头图路径',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

….实在是太多了详细代码请看

12.项目部署

本次就将项目部署在虚拟机上.

1.将 Springboot项目打包成可执行jar

需要到maven的一个插件

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

然后本别打包

后得到如下文件

在这里插入图片描述

上传到虚拟机上…

2.使用到docker容器部署

安装步骤省略….

拉取mysql redis jdk8 镜像.

  1. 分别启动mysql,redis
docker run -p 3306:3306 --name mysql  --network=crowd -v /docker/mysql/conf:/etc/mysql/conf.d -v /docker/mysql/logs:/logs \
-v /docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql


.....

4.进入容器启动java项目

java -jar crowd-eureka-1.0-SNAPSHOT.jar >eureka.log &
java -jar crowd-authentication-consumer-1.0-SNAPSHOT.jar >consumer.log &
java -jar crowd-gateway-1.0-SNAPSHOT.jar >gateway.log & 
java -jar crowd-mysql-provider-1.0-SNAPSHOT.jar >mysql-provider.log &
java -jar crowd-order-consumer-1.0-SNAPSHOT.jar >order-consumer.log &
java -jar crowd-pay-consumer-1.0-SNAPSHOT.jar >pay-consumer.log &
java -jar crowd-project-consumer-1.0-SNAPSHOT.jar >project-consumer.log &
java -jar crowd-redis-provider-1.0-SNAPSHOT.jar >redis-provider.log &
java -jar crowd-service-api-1.0-SNAPSHOT.jar >service-api.log &

在这里插入图片描述

请添加图片描述

13.结束

这个项目断断续续积累使用了 2022年3月27日15:07:51 - 2022年8月27日23:22:26 刚好五个月时间,其实前面功能差不多完成已经差不多了,只是还没有真真的部署,然后花了一些时间简单部署了一下.

前端使用的是 Vue3 + Vite 技术,后端主要是学习Spring….

其实这个项目给了我挺大的收获.在此过程遇到了挺多的问题…

1.spring ICO 容器 和SpringMVC 容器的关系

2.xml文件配置包扫描的规则……

3.zull网关的使用.由于听说已经过时了…所以也没有深究,然后使用 getaway 代替了 zull 作为网关

4.支付宝的回调问题…虽然支付成功了.但是由于使用了前后端分离,和教程的有所差别.也是本次项目最大的遗憾(未完成支付成功后,调到自己的页面)—–>其实用前端监听可以实现,但是我觉得应该不是这样解决的…

学到这里告下一段落了,学习的道路还很长,Go for it….

后端项目代码: https://github.com/1932794922/crowd_java

前端项目代码: https://github.com/1932794922/crowd_vue

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Crazy_August

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

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

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

打赏作者

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

抵扣说明:

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

余额充值