一、“发起项目” 建模

1、创建数据库表

  1. # 分类表
  2. CREATE TABLE t_type (
  3. id INT ( 11 ) NOT NULL auto_increment,
  4. name VARCHAR ( 255 ) COMMENT '分类名称',
  5. remark VARCHAR ( 255 ) COMMENT '分类介绍',
  6. PRIMARY KEY ( id )
  7. );
  8. # 项目分类中间表
  9. create table t_project_type (
  10. id int not null auto_increment,
  11. projectid int(11),
  12. typeid int(11),
  13. primary key (id)
  14. );
  15. # 标签表
  16. create table t_tag
  17. (
  18. id int(11) not null auto_increment,
  19. pid int(11),
  20. name varchar(255),
  21. primary key (id)
  22. );
  23. # 项目标签中间表
  24. create table t_project_tag
  25. (
  26. id int(11) not null auto_increment,
  27. projectid int(11),
  28. tagid int(11),
  29. primary key (id)
  30. );
  31. # 项目表
  32. create table t_project
  33. (
  34. id int(11) not null auto_increment,
  35. project_name varchar(255) comment '项目名称',
  36. project_description varchar(255) comment '项目描述',
  37. money bigint (11) comment '筹集金额',
  38. day int(11) comment '筹集天数',
  39. status int(4) comment '0-即将开始,1-众筹中,2-众筹成功,3-众筹失败',
  40. deploydate varchar(10) comment '项目发起时间',
  41. supportmoney bigint(11) comment '已筹集到的金额',
  42. supporter int(11) comment '支持人数',
  43. completion int(3) comment '百分比完成度',
  44. memberid int(11) comment '发起人的会员 id',
  45. createdate varchar(19) comment '项目创建时间',
  46. follower int(11) comment '关注人数',
  47. header_picture_path varchar(255) comment '头图路径',
  48. primary key (id)
  49. );
  50. # 项目表项目详情图片表中间表
  51. create table t_project_item_pic
  52. (
  53. id int(11) not null auto_increment,
  54. projectid int(11),
  55. item_pic_path varchar(255),
  56. primary key (id)
  57. );
  58. # 项目发起人信息表
  59. create table t_member_launch_info
  60. (
  61. id int(11) not null auto_increment,
  62. memberid int(11) comment '会员 id',
  63. description_simple varchar(255) comment '简单介绍',
  64. description_detail varchar(255) comment '详细介绍',
  65. phone_num varchar(255) comment '联系电话',
  66. service_num varchar(255) comment '客服电话',
  67. primary key (id)
  68. );
  69. # 回报信息表
  70. create table t_return
  71. (
  72. id int(11) not null auto_increment,
  73. projectid int(11),
  74. type int(4) comment '0 - 实物回报, 1 虚拟物品回报',
  75. supportmoney int(11) comment '支持金额',
  76. content varchar(255) comment '回报内容',
  77. count int(11) comment '回报产品限额,“0”为不限回报数量',
  78. signalpurchase int(11) comment '是否设置单笔限购',
  79. purchase int(11) comment '具体限购数量',
  80. freight int(11) comment '运费,“0”为包邮',
  81. invoice int(4) comment '0 - 不开发票, 1 - 开发票',
  82. returndate int(11) comment '项目结束后多少天向支持者发送回报',
  83. describ_pic_path varchar(255) comment '说明图片路径',
  84. primary key (id)
  85. );
  86. # 发起人确认信息表
  87. create table t_member_confirm_info
  88. (
  89. id int(11) not null auto_increment,
  90. memberid int(11) comment '会员 id',
  91. paynum varchar(200) comment '易付宝企业账号',
  92. cardnum varchar(200) comment '法人身份证号',
  93. primary key (id)
  94. );

2、逆向工程

10前台-发起项目 - 图1

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE generatorConfiguration
  3. PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  4. "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
  5. <generatorConfiguration>
  6. <context id="atguiguTables" targetRuntime="MyBatis3">
  7. <commentGenerator>
  8. <!-- 是否去除自动生成的注释 true:是 : false:否 -->
  9. <property name="suppressAllComments" value="true" />
  10. </commentGenerator>
  11. <!-- 数据库链接URL、用户名、密码 -->
  12. <jdbcConnection
  13. driverClass="com.mysql.cj.jdbc.Driver"
  14. connectionURL="jdbc:mysql://localhost:3306/project_rowd?serverTimezone=UTC"
  15. userId="root"
  16. password="root">
  17. </jdbcConnection>
  18. <!--
  19. 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer
  20. true,把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal
  21. -->
  22. <javaTypeResolver>
  23. <property name="forceBigDecimals" value="false" />
  24. </javaTypeResolver>
  25. <!--
  26. 生成model模型,对应的包路径,以及文件存放路径(targetProject),targetProject可以指定具体的路径,如./src/main/java,
  27. 也可以使用“MAVEN”来自动生成,这样生成的代码会在target/generatord-source目录下
  28. -->
  29. <!--<javaModelGenerator targetPackage="com.joey.mybaties.test.pojo" targetProject="MAVEN">-->
  30. <javaModelGenerator targetPackage="org.fall.entity.po" targetProject=".\src\main\java">
  31. <!--是否让schema作为包的后缀-->
  32. <property name="enableSubPackages" value="false"/>
  33. <!-- 从数据库返回的值被清理前后的空格 -->
  34. <property name="trimStrings" value="true" />
  35. </javaModelGenerator>
  36. <!--对应的mapper.xml文件 -->
  37. <sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\java">
  38. <!--是否让schema作为包的后缀-->
  39. <property name="enableSubPackages" value="false"/>
  40. </sqlMapGenerator>
  41. <!-- 对应的Mapper接口类文件 -->
  42. <javaClientGenerator type="XMLMAPPER" targetPackage="org.fall.mapper" targetProject=".\src\main\java">
  43. <!--是否让schema作为包的后缀-->
  44. <property name="enableSubPackages" value="false"/>
  45. </javaClientGenerator>
  46. <!-- 数据库表名与需要的实体类对应映射的指定 -->
  47. <table tableName="t_type" domainObjectName="TypePO" />
  48. <table tableName="t_tag" domainObjectName="TagPO" />
  49. <table tableName="t_project" domainObjectName="ProjectPO" />
  50. <table tableName="t_project_item_pic" domainObjectName="ProjectItemPicPO" />
  51. <table tableName="t_member_launch_info" domainObjectName="MemberLaunchInfoPO" />
  52. <table tableName="t_return" domainObjectName="ReturnPO" />
  53. <table tableName="t_member_confirm_info" domainObjectName="MemberConfirmInfoPO" />
  54. </context>
  55. </generatorConfiguration>

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

3、创建与浏览器对应的VO对象

10前台-发起项目 - 图2

详细内容见代码,与浏览器发来的数据相对应。

二、“发起项目”功能实现

总目标:把各个前端表单的数据汇总到一起,并存入各个数据库表中。

思路:

10前台-发起项目 - 图3

项目部分的页面与代码主要在crowdfunding13-member-project-consumer模块完成

项目发起部分

1、跳转到众筹页面

Zuul模块的配置文件application.yml中增加project模块的访问路径

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模块的配置文件先进行基础的配置:

server:
  port: 5000
spring:
  application:
    name: crowd-project
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
  redis:
    host: 192.168.0.101
  session:
    store-type: redis
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka/

我的众筹 -> 发起众筹按钮绑定单击响应函数,以跳转到发起众筹的页面

<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

@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        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");
    }
}

点击发起众筹后,进入协议同意的页面,给同意按钮绑定下一个页面(项目及发起人信息 页面)的url

<div class="panel-footer" style="text-align:center;">
    <a class="btn btn-warning btn-lg" href="project/launch/project/page" th:href="@{http://localhost/project/do/crowd/launch/page.html}">阅读并同意协议</a>
</div>

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

10前台-发起项目 - 图4

表单的action:

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

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

后端handler方法:

// 自动注入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";
}

上面方法中使用的CrowdUtil.uploadFileToOSS(ossProperties方法:

在CrowdUtil模块中需要引入OSS的依赖

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.5.0</version>
</dependency>
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();
    }

}

通过自动注入的 ossProperties给上面的工具方法传入参数

参数放在application.yml中

aliyun:
  oss:
    access-key-id: LTAI4G9FJwmy9GEHLWziB
    access-key-secret: tNUUkzf3pPEWmlYmuKSaW7skl4
    bucket-domain: http://fall2020.oss-cn-hangzhou.aliyuncs.com
    bucket-name: fall2020
    end-point: oss-cn-hangzhou.aliyuncs.com

收集回报信息部分

10前台-发起项目 - 图5

1、“上传图片”按钮

按钮的标签:

<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方法:

// 回报页面上传图片时触发的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、“确定”按钮

按钮的标签:

<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方法:

// 回报页面保存回报信息的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、“下一步”按钮

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

通过view-controller跳转到手机确认信息的页面

收集确认信息部分

1、前端页面

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

2、后端页面

@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";
}

3、API模块的远程方法:

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

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

controller层

// 将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层

@Service
public class ProjectServiceImpl implements ProjectService {

    @Autowired
    ProjectPOMapper projectPOMapper;

    @Autowired
    ProjectItemPicPOMapper projectItemPicPOMapper;

    @Autowired
    MemberLaunchInfoPOMapper memberLaunchInfoPOMapper;

    @Autowired
    ReturnPOMapper returnPOMapper;

    @Autowired
    MemberConfirmInfoPOMapper memberConfirmInfoPOMapper;

    @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得到自增的主键,
        // 在mapper的xml文件中对应的insert标签增加了useGeneratedKeys="true" keyProperty="id"的配置
        projectPOMapper.insertSelective(projectPO);


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



        // 二、保存项目、分类关联关系信息
        // 1.得到TypeList
        List<Integer> typeIdList = projectVO.getTypeIdList();
        // 2.执行保存操作
        projectPOMapper.saveTypeRelationship(projectId, typeIdList);


        // 三、保存项目、标签关联关系信息
        // 1. 得到TagIdList
        List<Integer> tagIdList = projectVO.getTagIdList();
        // 2.执行保存操作
        projectPOMapper.saveTagRelationship(projectId, tagIdList);


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

        // 2.执行保存操作
        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存入数据库
        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);

    }
}

Mapper因为量较多,这里省略了,可以在项目代码中直接查看