1.将阿里云OSS引入项目

1.开通阿里云OSS服务

image.png

2.准备OSSProperties代码

项目结构:
image.png
代码:

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @Component
  5. @ConfigurationProperties(prefix = "aliyun.oss")
  6. public class OSSProperties {
  7. private String endPoint;
  8. private String bucketName;
  9. private String accessKeyId;
  10. private String accessKeySecret;
  11. private String bucketDomain;
  12. }

3.加入依赖

项目结构:
image.png
代码:

<dependencies>
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.5.0</version>
        </dependency>
        <dependency>
            <groupId>com.zh.crowd</groupId>
            <artifactId>crowdfunding17-member-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <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-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

4.配置application.yml文件

项目结构:
image.png

server:
  port: 5000
spring:
  application:
    name: crowd-project
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html

  redis:
    host: 127.0.0.1
  session:
    store-type: redis
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka
aliyun:
  oss:
    access-key-id: LTAI5tJxzzVdkprdwXHHZi
    access-key-secret: jnpjfSwCuYFe4wcZrxuhMz33ubU
    bucket-domain: http://zhcrowd.oss-cn-shenzhen.aliyuncs.com
    bucket-name: zhcrowd
    end-point: oss-cn-shenzhen.aliyuncs.com

5.创建文件上传工具方法

1.加入依赖

项目结构:
image.png
代码:

<!--OSS客户端SDK-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.5.0</version>
        </dependency>

2.工具方法

项目结构:
image.png
代码:

public static ResultEntity<String> uploadFileToOSS(
        String endPoint,
        String accessKeyId,
        String accessKeySecret,
        InputStream inputStream,
        String bucketName,
        String bucketDomain,
        String originalName ){

    // 创建OSSClient实例
    OSS ossClient = new OSSClientBuilder().build(endPoint,accessKeyId,accessKeySecret);

    // 生成上传文件的目录,按照日期来划分目录
    String folderName = new SimpleDateFormat("yyyyMMdd").format(new Date());

    // 生成上传文件在OSS服务器上保存的文件名,通过uuid生成随机uuid,将其中的“-”删去(替换成空字符串)
    String fileMainName = UUID.randomUUID().toString().replace("-", "");

    // 从原始文件名中获取文件扩展名
    String extensionName = originalName.substring(originalName.lastIndexOf("."));

    // 使用目录、文件主体名称、文件扩展名拼接得到对象名称
    String objectName = folderName + "/" + fileMainName + extensionName;


    try {
        // 调用OSS客户端对象的方法上传文件并获取响应结果数据
        PutObjectResult putObjectResult = ossClient.putObject(bucketName,objectName,inputStream);

        // 从响应结果中获取具体的响应消息
        ResponseMessage responseMessage = putObjectResult.getResponse();

        // 根据响应状态判断是否成功
        if (responseMessage == null) {
            // 拼接访问刚刚上传的文件的路径
            String ossFileAccessPath = bucketDomain + "/" + objectName;

            // 返回成功,并带上访问路径
            return ResultEntity.successWithData(ossFileAccessPath);
        }else {
            // 获取响应状态码
            int statusCode = responseMessage.getStatusCode();

            // 没有成功 获取错误消息
            String errorMessage = responseMessage.getErrorResponseAsString();

            return ResultEntity.failed("当前响应状态码=" + statusCode + " 错误消息=" + errorMessage);
        }
    } catch (Exception e){
        e.printStackTrace();
        return ResultEntity.failed(e.getMessage());
    } finally {
        // 最后关闭OSSClient
        ossClient.shutdown();
    }

}

2.发起项目跳转

1.从个人中心跳转到发起项目的表单页面

1.点击“我的众筹”

项目结构:
image.png
代码修改:

<a th:href="@{/auth/to/member/crowd/page.html}" style="text-decoration: none">我的众筹</a>

2.view-controller代码

项目结构:
image.png
代码:

 // 前往“我的众筹”页面
        registry.addViewController("/auth/to/member/crowd/page.html").setViewName("member-crowd");

3.新建member-crowd.html

项目结构:
image.png
代码:(部分代码)

<div class="list-group">
                <div class="list-group-item " style="cursor:pointer;"><a th:href="@{/auth/to/member/center/page.html}" style="text-decoration: none">资产总览</a>
                    <span class="badge"><i class="glyphicon glyphicon-chevron-right"></i></span>
                </div>
                <div class="list-group-item active">
                    我的众筹<span class="badge"><i class="glyphicon glyphicon-chevron-right"></i></span>
                </div>
            </div>

3.发起项目建模

1.创建数据库表

# 分类表
CREATE TABLE t_type (
    id INT ( 11 ) NOT NULL auto_increment,
    name VARCHAR ( 255 ) COMMENT '分类名称',
    remark VARCHAR ( 255 ) COMMENT '分类介绍',
    PRIMARY KEY ( id ) 
);

# 项目分类中间表
create table t_project_type (
    id    int not null auto_increment,
    projectid    int(11),
    typeid    int(11),
    primary key (id)
);

# 标签表
create table t_tag
(    
    id    int(11) not null auto_increment,
    pid    int(11),
    name    varchar(255),
    primary key (id)
);

# 项目标签中间表
create table t_project_tag
(
    id    int(11) not null auto_increment,
    projectid    int(11),
    tagid    int(11),
    primary key (id)    
);    

# 项目表
create table t_project    
(    
    id    int(11) not null auto_increment,
    project_name    varchar(255) comment '项目名称',
    project_description    varchar(255) comment '项目描述',
    money    bigint (11) comment '筹集金额',
    day    int(11) comment '筹集天数',
    status    int(4) comment '0-即将开始,1-众筹中,2-众筹成功,3-众筹失败',    
    deploydate    varchar(10) comment '项目发起时间',
    supportmoney    bigint(11) comment '已筹集到的金额',
    supporter    int(11) comment '支持人数',
    completion    int(3) comment '百分比完成度',
    memberid    int(11) comment '发起人的会员 id',
    createdate    varchar(19) comment '项目创建时间',
    follower    int(11) comment '关注人数',
    header_picture_path    varchar(255) comment '头图路径',
    primary key (id)    
);    

# 项目表项目详情图片表中间表
create table t_project_item_pic
(
    id    int(11) not null auto_increment,
    projectid    int(11),
    item_pic_path    varchar(255),
    primary key (id)
);

# 项目发起人信息表
create table t_member_launch_info
(        
    id    int(11) not null auto_increment,
    memberid    int(11)    comment '会员 id',
    description_simple   varchar(255)   comment '简单介绍',
    description_detail   varchar(255)   comment '详细介绍',
    phone_num    varchar(255)   comment '联系电话',
    service_num    varchar(255)    comment '客服电话',
    primary key (id)        
);        

# 回报信息表
create table t_return        
(        
    id    int(11) not null auto_increment,
    projectid    int(11),    
    type    int(4) comment '0 - 实物回报, 1 虚拟物品回报',
    supportmoney    int(11) comment '支持金额',
    content    varchar(255) comment '回报内容',
    count    int(11) comment '回报产品限额,“0”为不限回报数量',
    signalpurchase    int(11) comment '是否设置单笔限购',
    purchase    int(11) comment '具体限购数量',
    freight    int(11) comment '运费,“0”为包邮',
    invoice    int(4) comment '0 - 不开发票, 1 - 开发票',
    returndate    int(11) comment '项目结束后多少天向支持者发送回报',
    describ_pic_path    varchar(255) comment '说明图片路径',
    primary key (id)        
);        

# 发起人确认信息表
create table t_member_confirm_info    
(        
    id    int(11) not null auto_increment,
    memberid    int(11) comment '会员 id',
    paynum    varchar(200) comment '易付宝企业账号',
    cardnum    varchar(200) comment '法人身份证号',
    primary key (id)        
);

2.进行逆向工程

项目结构:
image.png
修改代码:

 <!-- 数据库表名与需要的实体类对应映射的指定 -->
        <table tableName="t_type" domainObjectName="TypePO" />
        <table tableName="t_tag" domainObjectName="TagPO" />
        <table tableName="t_project" domainObjectName="ProjectPO" />
        <table tableName="t_project_item_pic" domainObjectName="ProjectItemPicPO" />
        <table tableName="t_member_launch_info" domainObjectName="MemberLaunchInfoPO" />
        <table tableName="t_return" domainObjectName="ReturnPO" />
        <table tableName="t_member_confirm_info" domainObjectName="MemberConfirmInfoPO" />

通过MAVEN工具生成实体类、mapper接口即mapper文件,并放入对应的各个目录。
实体类:
image.png
mapper接口:
image.png
mapper.xml文件:
image.png

3.新建VO实体类

image.png

4.发起项目功能

1.总目标

将各个表单页面提交的数据汇总到一起保存到数据库。

2.思路

发起项目-思路.png

3.代码

1.跳转到众筹页面

Zuul模块的配置文件application.yml中增加project模块的访问路径:
项目结构:
image.png
代码:

zuul:
  ignored-services: "*"       # 表示忽视直接通过application-name访问微服务,必须通过route
  sensitive-headers: "*"      # 在Zuul向其他微服务重定向时,保持原本的头信息(请求头、响应头)
  routes:                     # 指定网关路由
    crowd-protal:
      service-id: crowd-auth  # 对应application-name
      path: /**               # 表示直接通过根路径访问,必须加上**,否则多层路径无法访问
    crowd-project:
      service-id: crowd-project
      path: /project/**

project模块的配置文件先进行基础的配置:
项目结构:
image.png
代码:

server:
  port: 5000
spring:
  application:
    name: crowd-project
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html

  redis:
    host: 127.0.0.1
  session:
    store-type: redis
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka
aliyun:
  oss:
    access-key-id: LTAI5tJxzzVdkprdwXHHZird
    access-key-secret: jnpjfSwCuYFe4wcZrxuhMz33ubUPHa
    bucket-domain: http://zhcrowd.oss-cn-shenzhen.aliyuncs.com
    bucket-name: zhcrowd
    end-point: oss-cn-shenzhen.aliyuncs.com

给 我的众筹 —-> 发起众筹按钮绑定单击响应函数,以跳转到发起众筹的页面:
项目结构:
image.png
代码:

<li class=" pull-right">
    <button type="button" class="btn btn-warning" onclick="window.location.href='http://localhost/project/agree/protocol/page.html'">
        发起众筹
    </button>
</li>

在project模块的CrowdWebMvcConfig类中,配置view-controller:
项目结构:
image.png
代码:

@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // view-controller是在project项目内部定义的,所以这里是一个不经过Zuul访问的地址,
        // 所以这个路径前面不加路由规则中定义的前缀:“/project”
        registry.addViewController("/do/crowd/launch/page.html").setViewName("project-launch");
        registry.addViewController("/agree/protocol/page.html").setViewName("project-agree");
        registry.addViewController("/return/info/page.html").setViewName("project-return");
        registry.addViewController("/create/confirm/page.html").setViewName("project-confirm");
        registry.addViewController("/create/success.html").setViewName("project-success");
    }
}


2.处理项目及发起人信息页面

image.png
1.表单的action:
项目结构:
image.png
代码:

<form id="projectForm" th:action="@{/project/create/project/information}" method="post" enctype="multipart/form-data" class="form-horizontal">

2.通过下一步按钮,绑定单击响应函数,进行表单的提交操作:

// 点击下一步按钮提交表单
        $("#submitBtn").click(function(){

            // 将表单中标签id的值组成的数组转换成表单内的隐藏域
            for(var i = 0; i < tagIdList.length; i++) {
                var tagId = tagIdList[i];

                var hiddenInputHTML = "<input type='hidden' name='tagIdList' value='"+tagId+"' />";

                $("#projectForm").append(hiddenInputHTML);
            }

            // 提交表单
            $("#projectForm").submit();
        }

3.后端handler方法:
项目结构:
image.png
代码:

// 自动注入OSSProperties
@Autowired
private OSSProperties ossProperties;

/**
 * 进行创建项目操作,成功后进入回报页面
 * @param projectVO  前端表单的数据自动装入ProjectVO对象
 * @param headerPicture 前端上传的头图
 * @param detailPictureList 前端上传的详情图片的list
 * @param session 用于存放信息
 * @param modelMap 用于发生错误时传递信息
 * @return 进入下一步的页面
 * @throws IOException
 */
@RequestMapping("/create/project/information")
public String createProject(
        ProjectVO projectVO,
        MultipartFile headerPicture,
        List<MultipartFile> detailPictureList,
        HttpSession session,
        ModelMap modelMap ) throws IOException {

    // 一、完成头图的上传
    // 判断headerPicture对象是否为空
    boolean headerPictureEmpty = headerPicture.isEmpty();

    if (headerPictureEmpty){
        // 头图为空,存入提示信息,且返回原本的页面
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_HEADER_PIC_EMPTY);
        return "project-launch";
    }
    // 头图不为空 进行上传操作
    ResultEntity<String> headerPictureResultEntity = CrowdUtil.uploadFileToOSS(ossProperties.getEndPoint(),
            ossProperties.getAccessKeyId(),
            ossProperties.getAccessKeySecret(),
            headerPicture.getInputStream(),
            ossProperties.getBucketName(),
            ossProperties.getBucketDomain(),
            headerPicture.getOriginalFilename());
    // 判断是否上传成功
    String result = headerPictureResultEntity.getResult();
    if (ResultEntity.FAILED.equals(result)){
        // 上传失败
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_HEADER_PIC_UPLOAD_FAILED);
        return "project-launch";
    } else {
        // 上传成功
        // 得到存入OSS服务器的文件名
        String headerPicturePath = headerPictureResultEntity.getData();

        // 存入ProjectVO对象
        projectVO.setHeaderPicturePath(headerPicturePath);
    }

    // 二、完成详情图片的上传

    // 创建用于存放详情图片的路径的List对象
    List<String> detailPicturePathList = new ArrayList<>();

    // 判断详情图片是否为空
    if (detailPictureList == null || detailPictureList.size() == 0){
        // 详情图片为空,加入提示信息,返回原本页面
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_DETAIL_PIC_EMPTY);
        return "project-launch";
    }
    // 详情图片不为空 遍历List
    for (MultipartFile detailPicture : detailPictureList) {
        // 判断当前MultipartFile是否有效
        if (detailPicture.isEmpty()){
            // 当前图片为空,也返回原本的页面
            modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_DETAIL_PIC_EMPTY);
            return "project-launch";
        }
        // 不为空,开始存放详情图片
        ResultEntity<String> detailPictureResultEntity = CrowdUtil.uploadFileToOSS(ossProperties.getEndPoint(),
                ossProperties.getAccessKeyId(),
                ossProperties.getAccessKeySecret(),
                detailPicture.getInputStream(),
                ossProperties.getBucketName(),
                ossProperties.getBucketDomain(),
                detailPicture.getOriginalFilename());
        // 检查上传的结果
        String detailPictureResult = detailPictureResultEntity.getResult();
        if (ResultEntity.FAILED.equals(detailPictureResult)){
            // 上传失败
            modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_DETAIL_PIC_UPLOAD_FAILED);
            return "project-launch";
        }
        // 上传成功
        // 将当前上传后的路径放入list
        detailPicturePathList.add(detailPictureResultEntity.getData());
    }

    // 将detailPicturePathList存入ProjectVO对象
    projectVO.setDetailPicturePathList(detailPicturePathList);

    // 后续操作
    // 将ProjectVO对象放入session域
    session.setAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT, projectVO);

    // 进入下一个收集回报信息的页面
    return "redirect:http://localhost/project/return/info/page.html";
}

3.收集回报信息部分

发起项目-回报页面.png

1“上传图片”按钮

项目结构:
image.png
按钮的标签:

<button type="button" id="uploadBtn"
        class="btn btn-primary btn-lg active">上传图片
</button>

对应的单击响应函数:

    // 在文件上传框的值改变事件响应函数中预览并上传图片
    $("[name=returnPicture]").change(function (event) {

        var file = event.target.files[0];

        var url = window.url || window.webkitURL;

        var path = url.createObjectURL(file);

        $(this).next().next().next().next().attr("src", path).show();

        // 将上传的文件封装到FormData对象中
        var formData = new FormData();

        formData.append("returnPicture", file);

        // 发送Ajax请求上传文件
        $.ajax({
            "url": "[[@{/project/create/upload/return/picture.json}]]",
            "type": "post",
            "data": formData,
            "contentType": false,
            "processData": false,
            "dataType": "json",
            "success": function (response) {

                var result = response.result;

                if (result == "SUCCESS") {
                    alert("上传成功!");

                    // 如果上传成功,则从响应体数据中获取图片的访问路径
                    returnObj.describPicPath = response.data;
                }

                if (result == "FAILED") {
                    alert(response.message);
                }

            },
            "error": function (response) {
                alert(response.status + " " + response.statusText);
            }
        });

    });

后端handler方法:
项目结构:
image.png
代码:

// 回报页面上传图片时触发的ajax请求对应的handler方法
@ResponseBody
@RequestMapping("/create/upload/return/picture.json")
public ResultEntity<String> uploadReturnPicture(@RequestParam("returnPicture") MultipartFile returnPicture) throws IOException {
    // 判断是否是有效上传
    boolean pictureIsEmpty = returnPicture.isEmpty();
    if (pictureIsEmpty){
        // 如果上传文件为空
        ResultEntity.failed(CrowdConstant.MESSAGE_RETURN_PIC_EMPTY);
    }

    // 进行上传到OSS服务器的操作
    ResultEntity<String> returnPictureResultEntity = CrowdUtil.uploadFileToOSS(ossProperties.getEndPoint(),
            ossProperties.getAccessKeyId(),
            ossProperties.getAccessKeySecret(),
            returnPicture.getInputStream(),
            ossProperties.getBucketName(),
            ossProperties.getBucketDomain(),
            returnPicture.getOriginalFilename());

    // 返回上传结果
    return returnPictureResultEntity;
}

2、“确定”按钮

发起项目-回报页面.png
按钮的标签:
项目结构:
image.png
代码:

<button type="button" class="btn btn-primary" id="okBtn">确定</button>

对应的单击响应函数:

// 点击确定按钮,绑定单击响应函数
$("#okBtn").click(function () {

    // 1.收集表单数据
    returnObj.type = $("[name=type]:checked").val();
    returnObj.supportmoney = $("[name=supportmoney]").val();
    returnObj.content = $("[name=content]").val();
    returnObj.count = $("[name=count]").val();
    returnObj.signalpurchase = $("[name=signalpurchase]:checked").val();
    returnObj.purchase = $("[name=purchase]").val();
    returnObj.freight = $("[name=freight]").val();
    returnObj.invoice = $("[name=invoice]:checked").val();
    returnObj.returndate = $("[name=returndate]").val();

    // 2.发送Ajax请求
    $.ajax({
        "url": "[[@{/project/create/save/return.json}]]",
        "type": "post",
        "dataType": "json",
        "data": returnObj,
        "success": function (response) {
            var result = response.result;
            if (result == "SUCCESS") {
                alert("这一条保存成功!");

                // 使用returnObj填充表格
                var orderTd = "<td>" + (++order) + "</td>";
                var supportmoneyTd = "<td>" + returnObj.supportmoney + "</td>";
                var countTd = "<td>" + returnObj.count + "</td>";
                var signalpurchaseTd = "<td>" + (returnObj.signalpurchase == 0 ? "不限购" : ("限购" + returnObj.purchase)) + "</td>";
                var contentTd = "<td>" + returnObj.content + "</td>";
                var returndateTd = "<td>" + returnObj.returndate + "天以后返还</td>";
                var freightTd = "<td>" + (returnObj.freight == 0 ? "包邮" : returnObj.freight) + "</td>";
                var operationTd = "<td><button type='button' class='btn btn-primary btn-xs'><i class=' glyphicon glyphicon-pencil'></i></button>&nbsp;<button type='button' class='btn btn-danger btn-xs'><i class=' glyphicon glyphicon-remove'></i></button></td>";
                var trHTML = "<tr>" + orderTd + supportmoneyTd + countTd + signalpurchaseTd + contentTd + returndateTd + freightTd + operationTd + "</tr>";

                $("#returnTableBody").append(trHTML);

                $("#returnPictureImage").hide();
            }

            if (result == "FAILED") {
                alert("这一条保存失败!");
            }

            // 后续操作
            // 仅仅调用click()函数而不传入回调函数表示点击一下这个按钮
            $("#resetBtn").click();

            // 将表单部分div隐藏
            $(".returnFormDiv").hide();
        }
    });
});

对应的后端handler方法:
项目结构:
image.png
代码:

// 回报页面保存回报信息的ajax请求对应的方法
@ResponseBody
@RequestMapping("/create/save/return.json")
public ResultEntity<String> saveReturn(ReturnVO returnVO, HttpSession session) {
    try {
        // 从session域取出ProjectVO对象
        ProjectVO projectVO = (ProjectVO)session.getAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT);

        // 判断ProjectVO是否回null
        if (projectVO == null){
            return ResultEntity.failed(CrowdConstant.MESSAGE_TEMPLE_PROJECT_MISSING);
        }

        // ProjectVO不为null
        // 取出projectVO中的returnVOList
        List<ReturnVO> returnVOList = projectVO.getReturnVOList();

        // 判断取出的list是否为空或长度为0
        if (returnVOList == null || returnVOList.size() == 0){
            // 初始化returnVOList
            returnVOList = new ArrayList<>();
            // 存入projectVO
            projectVO.setReturnVOList(returnVOList);
        }
        // 向returnVOList中存放当前接收的returnVO
        returnVOList.add(returnVO);

        // 重新将ProjectVO存入session域
        session.setAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT,projectVO);

        // 全部操作正常完成,返回成功的ResultEntity
        return ResultEntity.successWithoutData();

    } catch (Exception e){
        e.printStackTrace();
        // 出现异常返回failed,带上异常信息
        return ResultEntity.failed(e.getMessage());
    }
}

3、“下一步”按钮

image.png
修改按钮:
项目结构:
image.png
代码:

<a th:href="@{/project/create/confirm/page.html}" class="btn btn-warning btn-lg">下一步</a>

添加view-controller:
通过view-controller跳转到手机确认信息的页面
项目结构:
image.png
代码:

registry.addViewController("/create/confirm/page.html").setViewName("project-confirm");

新建project-confirm.html页面
image.png

4.收集确认信息部分

1.点击提交按钮的HTML标签

项目结构:
image.png
代码:

<button type="button" id="submitBtn" class="btn  btn-warning btn-lg">提交</button>

2.给提交按钮绑定单击函数与修改form表单

项目结构:
image.png
代码:

<form id="confirmForm" th:action="@{/project/create/confirm.html}" method="post" role="form">
    <div class="form-group">
         <label for="exampleInputEmail1">易付宝企业账号:</label><input type="email" name="paynum" class="form-control" 
                                                                id="exampleInputEmail1" />
    </div>
    <div class="form-group">
         <label for="exampleInputPassword1">法人身份证号:</label><input type="password" name="cardnum" class="form-control" 
                                                                  id="exampleInputPassword1" />
    </div>
</form>
... ...
<script type="text/javascript">
    $(function(){
        $("#submitBtn").click(function(){
            $("#confirmForm").submit();
        });
    });
</script>
<button type="button" id="submitBtn" class="btn  btn-warning btn-lg">提交</button>

3.后端代码

handler方法:
项目结构:
image.png
代码:

@RequestMapping("/create/confirm.html")
public String saveConfirm(MemberConfirmInfoVO memberConfirmInfoVO,HttpSession session, ModelMap modelMap){
    // 从session域取出ProjectVO对象
    ProjectVO projectVO = (ProjectVO)session.getAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT);

    // 判断ProjectVO是否回null
    if (projectVO == null){
        // 这里不多做处理了,就直接抛出异常
        throw new RuntimeException(CrowdConstant.MESSAGE_TEMPLE_PROJECT_MISSING);
    }

    // ProjectVO正常,开始向其中存放MemberConfirmInfo
    projectVO.setMemberConfirmInfoVO(memberConfirmInfoVO);

    // 从session域中读取当前登录的用户
    LoginMemberVO loginMember = (LoginMemberVO)session.getAttribute(CrowdConstant.ATTR_NAME_LOGIN_MEMBER);
    Integer memberId = loginMember.getId();

    // 调用远程方法保存ProjectVO对象和当前登录的用户的id
    ResultEntity<String> saveResultEntity = mySQLRemoteService.saveProjectRemote(projectVO, memberId);

    String result = saveResultEntity.getResult();

    if (ResultEntity.FAILED.equals(result)){
        // 保存出错,返回确认的界面,并且携带错误的消息
        modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, saveResultEntity.getMessage());
        return "project-confirm";
    }

    // 保存正常完成,删除session中临时存放的ProjectVO
    session.removeAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT);

    // 进入成功页面
    return "redirect:http://localhost/project/create/success.html";
}

新建成功的页面:
image.png
配置view-controller:
image.png
代码:

registry.addViewController("/create/success.html").setViewName("project-success");

3.API模块的远程方法

项目结构:
image.png
代码:

@RequestMapping("/save/project/remote")
ResultEntity<String> saveProjectRemote(@RequestBody ProjectVO projectVO, @RequestParam("memberId") Integer memberId);

4.mysql-provider模块的对应代码

image.png
Handler层:
项目结构:
image.png
代码:

@RestController
public class ProjectProviderHandler {

    @Autowired
    ProjectService projectService;

    // 将ProjectVO中的数据存入各个数据库
    @RequestMapping("/save/project/remote")
    public ResultEntity<String> saveProjectRemote(@RequestBody ProjectVO projectVO, @RequestParam("memberId") Integer memberId){
        // 调用本地service进行保存
        try {
            projectService.saveProject(projectVO, memberId);
            return ResultEntity.successWithoutData();
        } catch (Exception e){
            e.printStackTrace();
            return ResultEntity.failed(e.getMessage());
        }

    }

}

service层:
项目结构:
image.png
代码:

@Transactional(readOnly = true)
@Service
public class ProjectServiceImpl implements ProjectService {

    @Autowired
    private ProjectPOMapper projectPOMapper;

    @Autowired
    ProjectItemPicPOMapper projectItemPicPOMapper;

    @Autowired
    MemberLaunchInfoPOMapper memberLaunchInfoPOMapper;

    @Autowired
    ReturnPOMapper returnPOMapper;

    @Autowired
    MemberConfirmInfoPOMapper memberConfirmInfoPOMapper;

    @Transactional(readOnly = false,propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    @Override
    public void saveProject(ProjectVO projectVO, Integer memberId) {

        // 一、保存ProjectPO对象
        // 1.初始化一个ProjectPO
        ProjectPO projectPO = new ProjectPO();

        // 2.利用工具方法,给ProjectPO赋值
        BeanUtils.copyProperties(projectVO,projectPO);

        // 3.给projectPO设置memberId
        projectPO.setMemberid(memberId);

        // 4.给projectPO设置项目创建时间
        String createDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        projectPO.setCreatedate(createDate);

        // 5.设置status=0,表示项目即将开始
        projectPO.setStatus(0);

        // 6.向数据库保存ProjectPO
        // 为了在ProjectPO得到自增的主键,
        // 在ProjectPOMapper.xml文件中对应的insert标签增加了useGeneratedKeys="true" keyProperty="id"的配置
        // insert id="insertSelective" useGeneratedKeys="true" keyProperty="id"
        projectPOMapper.insertSelective(projectPO);


        // 得到projectId
        Integer projectId = projectPO.getId();



        // 二、保存项目、分类关联关系信息
        // 1.得到TypeList
        List<Integer> typeIdList = projectVO.getTypeIdList();
        // 2.执行保存操作
        /**
         接口:
        saveTypeRelationship(@Param("projectId") Integer projectId, @Param("typeIdList") List<Integer> typeIdList);
       SQL语句:
            <insert id="saveTypeRelationship">
                insert into t_project_type(projectid,typeid)
                  values
                <foreach collection="typeIdList" item="typeId" separator=",">
                (#{projectId},#{typeId})
                </foreach>
           </insert>
         */
        projectPOMapper.saveTypeRelationship(projectId, typeIdList);


        // 三、保存项目、标签关联关系信息
        // 1. 得到TagIdList
        List<Integer> tagIdList = projectVO.getTagIdList();
        // 2.执行保存操作
        /**
         * 接口:
         * saveTagRelationship(@Param("projectId")  Integer projectId, @Param("tagIdList") List<Integer> tagIdList);
         * SQL语句:
         *   <insert id="saveTagRelationship">
         *     insert into t_project_tag(projectid,tagid)
         *     values
         *     <foreach collection="tagIdList" item="tagId" separator=",">
         *       (#{projectId},#{tagId})
         *     </foreach>
         *   </insert>
         */
        projectPOMapper.saveTagRelationship(projectId, tagIdList);


        // 四、保存项目中详情图片路径信息
        // 1.得到detailPicturePathList
        List<String> detailPicturePathList = projectVO.getDetailPicturePathList();

        // 2.执行保存操作
        /**
         * 接口:
         *    insertPathList(@Param("projectId") Integer projectId,
         *       @Param("detailPicturePathList") List<String> detailPicturePathList);
         *   SQL语句:
         *   <insert id="insertPathList">
         *     insert into t_project_item_pic (projectid, item_pic_path)
         *     values
         *     <foreach collection="detailPicturePathList" item="detailPicturePath" separator=",">
         *       (#{projectId}, #{detailPicturePath})
         *     </foreach>
         */
        projectItemPicPOMapper.insertPathList(projectId, detailPicturePathList);


        // 五、保存项目发起人信息
        // 1.得到发起人信息
        MemberLauchInfoVO memberLauchInfoVO = projectVO.getMemberLauchInfoVO();

        // 2.初始化MemberLaunchInfoPO
        MemberLaunchInfoPO memberLaunchInfoPO = new MemberLaunchInfoPO();

        // 3.给MemberLaunchInfoPO赋值
        BeanUtils.copyProperties(memberLauchInfoVO,memberLaunchInfoPO);

        // 4.设置MemberLaunchInfoPO的memberId
        memberLaunchInfoPO.setMemberid(memberId);

        // 5.保存发起人信息
        memberLaunchInfoPOMapper.insertSelective(memberLaunchInfoPO);


        // 六、保存项目回报信息
        // 1.得到项目汇报信息的List
        List<ReturnVO> returnVOList = projectVO.getReturnVOList();

        // 2.初始化一个ReturnPO的list
        List<ReturnPO> returnPOList = new ArrayList<>();

        // 3.遍历给ReturnPO赋值 并存入List
        for (ReturnVO returnVO : returnVOList){
            ReturnPO returnPO = new ReturnPO();
            BeanUtils.copyProperties(returnVO,returnPO);
            returnPOList.add(returnPO);
        }
        // 4.将returnPOList存入数据库
        /**
         * <!--
         *     insertReturnPOList(@Param("projectId") Integer projectId,
         *     @Param("returnPOList") List<ReturnPO> returnPOList);
         * -->
         *   <insert id="insertReturnPOList">
         *     insert into t_return (projectid,type,supportmoney,content,count,signalpurchase,
         *     purchase,freight,invoice,returndate,describ_pic_path)
         *      values
         *       <foreach collection="returnPOList" item="returnPO" separator=",">
         *         (
         *            #{projectId},#{returnPO.type},#{returnPO.supportmoney},#{returnPO.content},
         *            #{returnPO.count},#{returnPO.signalpurchase},#{returnPO.purchase},#{returnPO.freight},
         *            #{returnPO.invoice},#{returnPO.returndate},#{returnPO.describPicPath})
         *       </foreach>
         *   </insert>
         */
        returnPOMapper.insertReturnPOList(projectId,returnPOList);


        // 七、保存项目确认信息
        // 1.得到MemberConfirmInfoVO
        MemberConfirmInfoVO memberConfirmInfoVO = projectVO.getMemberConfirmInfoVO();

        // 2.初始化MemberConfirmInfoPO对象
        MemberConfirmInfoPO memberConfirmInfoPO = new MemberConfirmInfoPO();

        // 3.给MemberConfirmInfoPO赋值
        BeanUtils.copyProperties(memberConfirmInfoVO,memberConfirmInfoPO);

        // 4.给MemberConfirmInfoPO设置memberId
        memberConfirmInfoPO.setMemberid(memberId);

        // 将MemberConfirmInfoPO存入数据库
        memberConfirmInfoPOMapper.insertSelective(memberConfirmInfoPO);

    }
}