day15【前台】项目发布

1、OSS

1.1、开通OSS服务

  • 进入控制台,选择【对象存储OSS】

  • 立即开通

  • 同意协议

  • 开通完成后,前往控制台创建Bucket

1.2、创建Bucket

  • 创建Bucket

  • Bucket名称
  • 设置区域为附近区域
  • 设置为低频访问存储,适用于较少访问的服务
  • 不开通【版本控制】
  • 读写权限设置为公共读:读取无需登录,写入需要登录

1.3、页面上传文件

  • 新建目录用于存储文件

  • 创建多级目录

  • 选择上传文件

  • 上传本地图片至阿里云OSS

  • 图片的地址:Bucket域名+图片路径

1.4、创建子用户AccessKey

  • 一般我们不使用主账户(登录账户)来调用 API 接口,因为主账户的权限太大,一旦 key secret 泄露,就玩完。。。
  • 一般创建子账户来调用 OSS API 接口,点击用户头像,选择 AccessKey管理

  • 选择开始使用子用户 AccessKey

  • 设计登录名称显示名称,选择编程访问(自己编写的程序调用或 PicGo 等第三方程序调用)

  • Key Secret貌似仅此一次机会可以查看,赶紧保存下来

1.5、子用户AccessKey授权

  • 给创建的子用户添加权限

  • 添加 OSS 服务:AliyunOSSFullAccess ,FullAccess 表示可读可写

1.6、project-consumer环境

1.6.1、引入依赖

  • project-consumer工程中引入所需的依赖

<dependencies><!-- api工程 --><dependency><groupId>com.atguigu.crowd</groupId><artifactId>atcrowdfunding17-member-api</artifactId><version>0.0.1-SNAPSHOT</version></dependency><!-- yml配置文件智能提示 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><!-- web标配 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 单元测试 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- thymeleaf模板引擎 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- eureka-client --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!-- 引入springboot&redis整合场景 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 引入springboot&springsession整合场景 --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>
</dependencies>

1.6.2、创建主启动类

//启用Feign客户端功能
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class CrowdMainClass {public static void main(String[] args) {SpringApplication.run(CrowdMainClass.class, args);}}

1.6.3、准备OSSProperties类

  • 创建OSSProperties配置类

@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class OSSProperties {private String endPoint;private String bucketName;private String accessKeyId;private String accessKeySecret;private String bucketDomain;}

1.6.4、创建yml配置文件

  • 微服务端口号
  • 微服务名称
  • Thymeleaf模板引擎
  • SpringSession相关配置
  • 指定Eureka注册中心的地址
  • 阿里云OSS基本配置

server:port: 5000spring:application:name: atguigu-crowd-projectthymeleaf:prefix: classpath:/templates/suffix: .htmlredis:host: 192.168.152.129session:store-type: rediseureka:client:service-url:defaultZone: http://localhost:1000/eurekaaliyun:oss:access-key-id: <填入你的access-key-id>access-key-secret: <填入你的access-key-secret>bucket-domain: <填入你的bucket-domain>bucket-name: <填入你的bucket-name>end-point: <填入你的end-point>
  • bucket-domainend-point去阿里云控制台复制,bucket-name就是heygo(当初创建Bucket时取的名字)

1.7、上传文件工具方法

1.7.1、引入依赖

  • util工程引入阿里云OSS服务所需的依赖

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

1.7.2、创建工具方法

  • 在工具类中添加如下方法

/*** 专门负责上传文件到OSS服务器的工具方法* @param endpoint          OSS参数* @param accessKeyId      OSS参数* @param accessKeySecret  OSS参数* @param inputStream      要上传的文件的输入流* @param bucketName      OSS参数* @param bucketDomain     OSS参数* @param originalName     要上传的文件的原始文件名* @return  包含上传结果以及上传的文件在OSS上的访问路径*/
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服务器上保存时的文件名// 原始文件名:beautfulgirl.jpg// 生成文件名:wer234234efwer235346457dfswet346235.jpg// 使用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 {if(ossClient != null) {// 关闭OSSClient。ossClient.shutdown();}}}

1.8、测试

1.8.1、创建测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class OSSTest {@Autowiredprivate OSSProperties oSSProperties;@Testpublic void testOSS() throws FileNotFoundException {FileInputStream inputStream = new FileInputStream("SpongeBob.jpg");ResultEntity<String> resultEntity = CrowdUtil.uploadFileToOss(oSSProperties.getEndPoint(),oSSProperties.getAccessKeyId(), oSSProperties.getAccessKeySecret(), inputStream,oSSProperties.getBucketName(), oSSProperties.getBucketDomain(), "SpongeBob.jpg");System.out.println(resultEntity.getResult());}}
  • 图片放置路径:工程根目录下

1.8.2、测试结果

  • 控制台打印
SUCCESS
  • OSS上传图片成功

2、跳转到发起项目页面

2.1、整体思路

2.2、解决Zuul相关问题

2.2.1、序列化

  • SpringSession存储对象至Redis中,要求该类必须是可序列化的,否则会抛异常

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberLoginVO implements Serializable {private static final long serialVersionUID = 1L;private Integer id;private String username;private String email;}

2.2.2、session中数据莫名消失

1、问题描述
  • 明明 在登陆成功之后,将MemberLoginVO对象存入了session,却取不出来,提示sessionnull
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "session.loginMember.username" (template: "member-center" - line 69, col 121)
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'username' cannot be found on null

2、问题分析
  • auth-consumer所属域名为http://localhost:4000zuul所属域名为http://localhost:80,他他俩属于不同的域名, 浏览器工作时不会使用相同的 Cookie,自然session就不一样(不共享)咯
3、解决方法
  • 以后重定向的地址都按照通过 Zuul 访问的方式写地址

@RequestMapping("/auth/member/do/login")
public String login(@RequestParam("loginacct") String loginacct, @RequestParam("userpswd") String userpswd,ModelMap modelMap,HttpSession session) {// 1.调用远程接口根据登录账号查询MemberPO对象ResultEntity<MemberPO> resultEntity = mySQLRemoteService.getMemberPOByLoginAcctRemote(loginacct);if(ResultEntity.FAILED.equals(resultEntity.getResult())) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, resultEntity.getMessage());return "member-login";}MemberPO memberPO = resultEntity.getData();if(memberPO == null) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_LOGIN_FAILED);return "member-login";}// 2.比较密码String userpswdDataBase = memberPO.getUserpswd();BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();boolean matcheResult = passwordEncoder.matches(userpswd, userpswdDataBase);if(!matcheResult) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_LOGIN_FAILED);return "member-login";}// 3.创建MemberLoginVO对象存入Session域MemberLoginVO memberLoginVO = new MemberLoginVO(memberPO.getId(), memberPO.getUsername(), memberPO.getEmail());session.setAttribute(CrowdConstant.ATTR_NAME_LOGIN_MEMBER, memberLoginVO);// 使用重定向避免刷新浏览器导致重新执行注册流程// 以后重定向的地址都按照通过 Zuul访问的方式写地址 return "redirect:http://www.crowd.com/auth/member/to/center/page";
}
// 使用重定向避免刷新浏览器导致重新执行注册流程
// 以后重定向的地址都按照通过 Zuul访问的方式写地址
return "redirect:http://www.crowd.com/auth/member/to/center/page";
  • 这里只是以一个Handler方法作为示例,在工程中所有重定向的前面,都加上域名:http://www.crowd.com

2.2.3、页面跳转地址

1、前置域名问题
  • 需要注意: 前面要写上域名(如果没有配置域名写 localhost 一样), 确保通过Zuul访问具体功能。
  • 因为必须通过 Zuul 访问具体微服务才能够保持 Cookie, 进而保持 Session 一致
2、Cookie之Domain
  • Domain保持一致,Cookie才能保持一致,session才能保持一致

3、举例
  • 【退出系统】按钮的超链接

<a href="http://www.crowd.com/auth/member/logout">退出系统</a>
  • 退出成功后,页面重定向至http://www.crowd.com

2.2.4、ClassNotFoundException

1、问题描述
  • ClassNotFoundException异常
Caused by: java.lang.ClassNotFoundException: com.atguigu.crowd.entity.vo.MemberLoginVO
2、引入依赖
  • ZuulFilter中会从Redis中读取MemberLoginVO对象的信息,并进行反序列化,所以我们需要在zuul工程中引入entity工程的依赖
<!-- entity实体类工程 -->
<dependency><groupId>com.atguigu.crowd</groupId><artifactId>atcrowdfunding09-member-entity</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>

2.2.5、测试

  • 正常访问~~~并且域名是www.crowd.com

2.3、我的众筹

2.3.1、修改超链接

  • 修改【我的众筹】超链接

<div class="list-group-item " style="cursor:pointer;"><a th:href="@{/member/my/crowd}">我的众筹</a><span class="badge"><i class="glyphicon glyphicon-chevron-right"></i></span>
</div>

2.3.2、添加view-controller

  • 通过view-controller进行跳转,/member/my/crowd地址对应着templates/member-crowd.html页面

@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {// 添加view-controller// 浏览器访问的地址  // 目标视图的名称,将来拼接“prefix: classpath:/templates/”、“suffix: .html”前后缀registry.addViewController("/auth/member/to/reg/page").setViewName("member-reg");registry.addViewController("/auth/member/to/login/page").setViewName("member-login");registry.addViewController("/auth/member/to/center/page").setViewName("member-center");registry.addViewController("/member/my/crowd").setViewName("member-crowd");}}

2.3.3、众筹页面

  • 添加众筹页面,并添加用户信息的回显

<h3>[[${session.loginMember.username}]]</h3>

2.3.4、测试

  • 点击【我的众筹】,正常跳转

3、发起项目

3.1、整体思路

3.2、创建数据库表

3.2.1、分类表

  • 分类表用于存储项目的分类信息
CREATE TABLE t_type (id INT (11) NOT NULL AUTO_INCREMENT,NAME VARCHAR (255) COMMENT      '分类名称',remark VARCHAR (255) COMMENT       '分类介绍',PRIMARY KEY (id)
)

3.2.2、项目分类中间表

  • 项目分类中间表用于存储项目与分类之间的对应关系
CREATE TABLE t_project_type (id INT NOT NULL AUTO_INCREMENT,projectid INT (11),typeid INT (11),PRIMARY KEY (id)
) ;

3.2.3、标签表

  • 标签表用于存储项目的标签信息
create table t_tag (id int (11) not null auto_increment,pid int (11),name varchar (255),primary key (id)
) ;

3.2.4、项目标签中间表

  • 项目标签中间表用于存储项目与标签之间的对应关系
CREATE TABLE t_project_tag (id INT (11) NOT NULL AUTO_INCREMENT,projectid INT (11),tagid INT (11),PRIMARY KEY (id)
) ;

3.2.5、项目表

  • 项目表用于存储项目的详细信息
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)
) ;

3.2.6、项目详情图片表

  • 由于用户可以上传多张项目详情图片,所以我们单独建个表存储项目详情图片
CREATE TABLE t_project_item_pic (id INT (11) NOT NULL AUTO_INCREMENT,projectid INT (11),item_pic_path VARCHAR (255),PRIMARY KEY (id)
) ;

3.2.7、项目发起人信息表

  • 项目发起人信息表用于存储项目发起人的详细信息
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)
) ;

3.2.8、回报信息表

  • 回报信息表用于存储众筹的回报信息
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)
) ;

3.2.9、发起人确认信息表

  • 发起人确认信息表用于存储发起人的详细信息
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)
)

3.3、逆向工程

3.3.1、逆向工程配置文件

  • 注意:不生成中间表对应的实体类

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfigurationPUBLIC "-//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="atguiguTables" targetRuntime="MyBatis3"><commentGenerator><!-- 是否去除自动生成的注释 true:是;false:否 --><property name="suppressAllComments" value="true" /></commentGenerator><!--数据库连接的信息:驱动类、连接地址、用户名、密码 --><jdbcConnection driverClass="com.mysql.jdbc.Driver"connectionURL="jdbc:mysql://localhost:3306/project_crowd" userId="root"password="root"></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="com.atguigu.crowd.entity.po"><!-- enableSubPackages:是否让schema作为包的后缀 --><property name="enableSubPackages" value="false" /><!-- 从数据库返回的值被清理前后的空格 --><property name="trimStrings" value="true" /></javaModelGenerator><!-- targetProject:XxxMapper.xml映射文件生成的路径 --><sqlMapGenerator targetProject=".\src\main\java"targetPackage="com.atguigu.crowd.mapper"><!-- enableSubPackages:是否让schema作为包的后缀 --><property name="enableSubPackages" value="false" /></sqlMapGenerator><!-- targetPackage:Mapper接口生成的位置 --><javaClientGenerator type="XMLMAPPER"targetProject=".\src\main\java"targetPackage="com.atguigu.crowd.mapper"><!-- enableSubPackages:是否让schema作为包的后缀 --><property name="enableSubPackages" value="false" /></javaClientGenerator><!-- 数据库表名字和我们的entity类对应的映射指定 --><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" /></context>
</generatorConfiguration>
<javaModelGenerator targetProject=".\src\main\java"targetPackage="com.atguigu.crowd.entity.po">...
</javaModelGenerator>
<!-- 数据库表名字和我们的entity类对应的映射指定 -->
<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" />

3.3.2、执行逆向工程

  • Maven Build命令:mybatis-generator:generate

3.3.3、资源归位

1、实体类

2、Mapper接口

3、Mapper.xml映射文件

3.4、创建组件

3.4.1、ProjectHandler

3.4.2、ProjectService

3.5、创建VO类

  • ProjectVO:项目详细信息
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProjectVO implements Serializable {private static final long serialVersionUID = 1L;   // 分类id集合private List<Integer> typeIdList;// 标签id集合private List<Integer> tagIdList;// 项目名称private String projectName;// 项目描述private String projectDescription;// 计划筹集的金额private Integer money;// 筹集资金的天数private Integer day;// 创建项目的日期private String createdate;// 头图的路径private String headerPicturePath;// 详情图片的路径private List<String> detailPicturePathList;// 发起人信息private MemberLauchInfoVO memberLauchInfoVO;// 回报信息集合private List<ReturnVO> returnVOList;// 发起人确认信息private MemberConfirmInfoVO memberConfirmInfoVO;
}
  • MemberLauchInfoVO:项目发起人信息
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberLauchInfoVO  implements Serializable {private static final long serialVersionUID = 1L;  // 简单介绍private String descriptionSimple;// 详细介绍private String descriptionDetail;// 联系电话private String phoneNum;// 客服电话private String serviceNum;}
  • ReturnVO:回报信息
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ReturnVO  implements Serializable {private static final long serialVersionUID = 1L;// 回报类型:0 - 实物回报, 1 虚拟物品回报private Integer type;// 支持金额private Integer supportmoney;// 回报内容介绍private String content;// 总回报数量,0为不限制private Integer count;// 是否限制单笔购买数量,0表示不限购,1表示限购private Integer signalpurchase;// 如果单笔限购,那么具体的限购数量private Integer purchase;// 运费,“0”为包邮private Integer freight;// 是否开发票,0 - 不开发票, 1 - 开发票private Integer invoice;// 众筹结束后返还回报物品天数private Integer returndate;// 说明图片路径private String describPicPath;}
  • MemberConfirmInfoVO:发起人确认信息
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberConfirmInfoVO implements Serializable {private static final long serialVersionUID = 1L;// 易付宝账号private String paynum;// 法人身份证号private String cardnum;
}

3.6、zuul路由规则

  • zuul中配置atguigu-crowd-project的路由规则:/project/**
  • 注意:
    • 如果一个请求需要经过zuul,请求路径必须带上/project部分
    • 如果一个请求无需经过zuul,只在atguigu-crowd-project微服务中做转发(比如配置view-controller等),那么请求路径就不能带/project部分

zuul:ignored-services: "*"sensitive-headers: "*"        # 在Zuul向其他微服务重定向时保持原本头信息(请求头、响应头)routes:crowd-portal:service-id: atguigu-crowd-authpath: /**                 # 这里一定要使用两个“*”号,不然“/”路径后面的多层路径将无法访问crowd-project:service-id: atguigu-crowd-projectpath: /project/**         # 这里一定要使用两个“*”号,不然“/”路径后面的多层路径将无法访问

3.7、跳转至发起项目页面

3.7.1、点击发起众筹

  • 修改【发起众筹】按钮的跳转地址

    • window.location.href = "http://www.crowd.com/project/agree/protocol/page";表示浏览器的跳转地址

    • 一定要经过zuul网关再进行跳转,否则会出现session不一致的问题

<li class=" pull-right"><script type="text/javascript">$(function(){$("#launchCrowdBtn").click(function(){window.location.href = "http://www.crowd.com/project/agree/protocol/page";});});</script><button id="launchCrowdBtn" type="button"class="btn btn-warning">发起众筹</button></li>

3.7.2、配置view-controller

  • project-consumer中配置view-controller
  • 因为view-controller是在project-consumer内部定义的,所以这里是一个不经过Zuul访问的地址,所以这个路径前面不加路由规则中定义的前缀:/project

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

3.7.3、用户协议页面

  • project-consumer工程中,创建用户协议页面、发起项目页面

3.7.4、测试

  • 点击【发起众筹】跳转至【用户协议】页面
  • 再点击【阅读并同意协议】,则可以跳转至发起项目页面

3.8、常量定义

public class CrowdConstant {public static final String MESSAGE_LOGIN_FAILED = "抱歉!账号密码错误!请重新输入!";public static final String MESSAGE_LOGIN_ACCT_ALREADY_IN_USE = "抱歉!这个账号已经被使用了!";public static final String MESSAGE_ACCESS_FORBIDEN = "请登录以后再访问!";public static final String MESSAGE_STRING_INVALIDATE = "字符串不合法!请不要传入空字符串!";public static final String MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE = "系统错误:登录账号不唯一!";public static final String MESSAGE_ACCESS_DENIED = "抱歉!您不能访问这个资源!";public static final String MESSAGE_CODE_NOT_EXISTS = "验证码已过期!请检查手机号是否正确或重新发送!";public static final String MESSAGE_CODE_INVALID = "验证码不正确!";public static final String MESSAGE_HEADER_PIC_UPLOAD_FAILED = "头图上传失败!";public static final String MESSAGE_HEADER_PIC_EMPTY = "头图不可为空!";public static final String MESSAGE_DETAIL_PIC_EMPTY = "详情图片不可为空!";public static final String MESSAGE_DETAIL_PIC_UPLOAD_FAILED = "详情图片上传失败!";public static final String MESSAGE_TEMPLE_PROJECT_MISSING = "临时存储的Project对象丢失!";public static final String ATTR_NAME_EXCEPTION = "exception";public static final String ATTR_NAME_LOGIN_ADMIN = "loginAdmin";public static final String ATTR_NAME_LOGIN_MEMBER = "loginMember";public static final String ATTR_NAME_PAGE_INFO = "pageInfo";public static final String ATTR_NAME_MESSAGE = "message";public static final String ATTR_NAME_TEMPLE_PROJECT = "tempProject";public static final String REDIS_CODE_PREFIX = "REDIS_CODE_PREFIX_";}

3.9、提交项目详情信息

3.9.1、目标

  • 将如下表单信息封装到ProjectVO对象中,保存至session域中,以便后续收集完成,保存至数据库中

3.9.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.9.3、Handler方法

  • 创建ProjectConsumerHandler,用于接收表单提交的数据

    • 接收表单提交的ProjectVO对象
    • 接收上传的头图,上传至OSS,并设置图片的网络路径
    • 接收项目的详情图片,上传至OSS,并设置图片的网络路径
    • ProjectVO对象存入session域中,并转发至回报页面

@Controller
public class ProjectConsumerHandler {@Autowiredprivate OSSProperties ossProperties;@RequestMapping("/create/project/information")public String saveProjectBasicInfo(// 接收除了上传图片之外的其他普通数据ProjectVO projectVO, // 接收上传的头图MultipartFile headerPicture, // 接收上传的详情图片List<MultipartFile> detailPictureList, // 用来将收集了一部分数据的ProjectVO对象存入Session域HttpSession session,// 用来在当前操作失败后返回上一个表单页面时携带提示消息ModelMap modelMap) throws IOException {// 一、完成头图上传// 1.获取当前headerPicture对象是否为空boolean headerPictureIsEmpty = headerPicture.isEmpty();if(headerPictureIsEmpty) {// 2.如果没有上传头图则返回到表单页面并显示错误消息modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_HEADER_PIC_EMPTY);return "project-launch";}// 3.如果用户确实上传了有内容的文件,则执行上传ResultEntity<String> uploadHeaderPicResultEntity = CrowdUtil.uploadFileToOss(ossProperties.getEndPoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret(), headerPicture.getInputStream(), ossProperties.getBucketName(), ossProperties.getBucketDomain(), headerPicture.getOriginalFilename());String result = uploadHeaderPicResultEntity.getResult();// 4.判断头图是否上传成功if(ResultEntity.SUCCESS.equals(result)) {// 5.如果成功则从返回的数据中获取图片访问路径String headerPicturePath = uploadHeaderPicResultEntity.getData();// 6.存入ProjectVO对象中projectVO.setHeaderPicturePath(headerPicturePath);} else {// 7.如果上传失败则返回到表单页面并显示错误消息modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_HEADER_PIC_UPLOAD_FAILED);return "project-launch";}// 二、上传详情图片// 1.创建一个用来存放详情图片路径的集合List<String> detailPicturePathList = new ArrayList<String>();// 2.检查detailPictureList是否有效if(detailPictureList == null || detailPictureList.size() == 0) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_DETAIL_PIC_EMPTY);return "project-launch";}// 3.遍历detailPictureList集合for (MultipartFile detailPicture : detailPictureList) {// 4.当前detailPicture是否为空if(detailPicture.isEmpty()) {// 5.检测到详情图片中单个文件为空也是回去显示错误消息modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_DETAIL_PIC_EMPTY);return "project-launch";}// 6.执行上传ResultEntity<String> detailUploadResultEntity = CrowdUtil.uploadFileToOss(ossProperties.getEndPoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret(), detailPicture.getInputStream(), ossProperties.getBucketName(), ossProperties.getBucketDomain(), detailPicture.getOriginalFilename());// 7.检查上传结果String detailUploadResult = detailUploadResultEntity.getResult();if(ResultEntity.SUCCESS.equals(detailUploadResult)) {String detailPicturePath = detailUploadResultEntity.getData();// 8.收集刚刚上传的图片的访问路径detailPicturePathList.add(detailPicturePath);} else {// 9.如果上传失败则返回到表单页面并显示错误消息modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_DETAIL_PIC_UPLOAD_FAILED);return "project-launch";}}// 10.将存放了详情图片访问路径的集合存入ProjectVO中projectVO.setDetailPicturePathList(detailPicturePathList);// 三、后续操作// 1.将ProjectVO对象存入Session域session.setAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT, projectVO);// 2.以完整的访问路径前往下一个收集回报信息的页面return "redirect:http://www.crowd.com/project/return/info/page";}}

3.9.4、添加view-controller

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

3.10、添加回报页面

3.11、测试

  • 图片能上传至阿里与OSS

  • Redis中的session

4、提交回报信息

4.1、整体思路

4.2、Ajax上传图片

4.2.1、前端Ajax请求

  • Ajax形式提交图片,并通过响应信息获取图片的网络位置
  • "url":"[[@{/project/create/upload/return/picture.json}]]":在页面地址中需要带上zuul网关的路由规则,即域名+微服务路径project

// 在文件上传框的值改变事件响应函数中预览并上传图片
$("[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);}});});

4.2.2、后端Handler代码

  • 将图片上传至OSS,并返回上传的结果

// JavaScript代码:formData.append("returnPicture", file);
// returnPicture是请求参数的名字
// file是请求参数的值,也就是要上传的文件
@ResponseBody
@RequestMapping("/create/upload/return/picture.json")
public ResultEntity<String> uploadReturnPicture(// 接收用户上传的文件@RequestParam("returnPicture") MultipartFile returnPicture) throws IOException {// 1.执行文件上传ResultEntity<String> uploadReturnPicResultEntity = CrowdUtil.uploadFileToOss(ossProperties.getEndPoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret(), returnPicture.getInputStream(), ossProperties.getBucketName(), ossProperties.getBucketDomain(), returnPicture.getOriginalFilename());// 2.返回上传的结果return uploadReturnPicResultEntity;
}

4.3、Ajax提交表单

4.3.1、前端Ajax请求

  • 收集表单项,提交Ajax请求

// 声明序号保存表格中数据的序号
var order = 0;// 点击确定按钮,绑定单击响应函数
$("#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();}});
});

4.3.2、后端Handler代码

  • 取出Redis中的ProjectVO对象,用该对象接收表单发送的ReturnVO对象,更新session中的ProjectVO对象

@ResponseBody
@RequestMapping("/create/save/return.json")
public ResultEntity<String> saveReturn(ReturnVO returnVO, HttpSession session) {try {// 1.从session域中读取之前缓存的ProjectVO对象ProjectVO projectVO = (ProjectVO) session.getAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT);// 2.判断projectVO是否为nullif(projectVO == null) {return ResultEntity.failed(CrowdConstant.MESSAGE_TEMPLE_PROJECT_MISSING);}// 3.从projectVO对象中获取存储回报信息的集合List<ReturnVO> returnVOList = projectVO.getReturnVOList();// 4.判断returnVOList集合是否有效if(returnVOList == null || returnVOList.size() == 0) {// 5.创建集合对象对returnVOList进行初始化returnVOList = new ArrayList<>();// 6.为了让以后能够正常使用这个集合,设置到projectVO对象中projectVO.setReturnVOList(returnVOList);}// 7.将收集了表单数据的returnVO对象存入集合returnVOList.add(returnVO);// 8.把数据有变化的ProjectVO对象重新存入Session域,以确保新的数据最终能够存入Redissession.setAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT, projectVO);// 9.所有操作成功完成返回成功return ResultEntity.successWithoutData();} catch (Exception e) {e.printStackTrace();return ResultEntity.failed(e.getMessage());}}

4.4、测试

  • 回报信息提交成功

  • 阿里云OSS上传图片成功

5、提交确认信息

5.1、创建页面

  • project-confirm.html:确认信息页面
  • project-success.html:成功页面

5.2、修改【下一步】超链接

  • 修改回报信息页面【下一步】按钮的超链接,点击【下一步】按钮,跳转至成功页面

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

5.3、添加view-controller

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

5.4、确认信息表单

  • action提交地址与Handler方法对应
  • 表单标签项的name属性与VO实体类属性名对应

<form id="confirmFomr" th:action="@{/project/create/confirm}" 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>

5.5、提交按钮

  • 点击【提交】按钮提交表单

<script type="text/javascript">$(function(){$("#submitBtn").click(function(){$("#confirmFomr").submit();});});
</script>
<button type="button" id="submitBtn" class="btn  btn-warning btn-lg">提交</button>

5.6、Handler代码

  • session域中取出ProjectVO对象
  • 更新ProjectVO对象的确认信息
  • ProjectVO对象提交至mysql-provider进行保存
  • 保存成功则删除session域中的ProjectVO对象,并重定向至成功页面

@Autowired
private MySQLRemoteService mySQLRemoteService;@RequestMapping("/create/confirm")
public String saveConfirm(ModelMap modelMap, HttpSession session, MemberConfirmInfoVO memberConfirmInfoVO) {// 1.从Session域读取之前临时存储的ProjectVO对象ProjectVO projectVO = (ProjectVO) session.getAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT);// 2.如果projectVO为nullif(projectVO == null) {throw new RuntimeException(CrowdConstant.MESSAGE_TEMPLE_PROJECT_MISSING);}// 3.将确认信息数据设置到projectVO对象中projectVO.setMemberConfirmInfoVO(memberConfirmInfoVO);// 4.从Session域读取当前登录的用户MemberLoginVO memberLoginVO = (MemberLoginVO) session.getAttribute(CrowdConstant.ATTR_NAME_LOGIN_MEMBER);Integer memberId = memberLoginVO.getId();// 5.调用远程方法保存projectVO对象ResultEntity<String> saveResultEntity = mySQLRemoteService.saveProjectVORemote(projectVO, memberId);// 6.判断远程的保存操作是否成功String result = saveResultEntity.getResult();if(ResultEntity.FAILED.equals(result)) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, saveResultEntity.getMessage());return "project-confirm";}// 7.将临时的ProjectVO对象从Session域移除session.removeAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT);// 8.如果远程保存成功则跳转到最终完成页面return "redirect:http://www.crowd.com/project/create/success";
}

5.7、api远程调用接口声明

  • MySQLRemoteService远程调用接口中声明上述方法

@FeignClient("atguigu-crowd-mysql")
public interface MySQLRemoteService {@RequestMapping("/get/memberpo/by/login/acct/remote")ResultEntity<MemberPO> getMemberPOByLoginAcctRemote(@RequestParam("loginacct") String loginacct);@RequestMapping("/save/member/remote")public ResultEntity<String> saveMember(@RequestBody MemberPO memberPO);@RequestMapping("/save/project/vo/remote")ResultEntity<String> saveProjectVORemote(@RequestBody ProjectVO projectVO, @RequestParam("memberId") Integer memberId);}

6、执行保存

6.1、Handler代码

  • Handler中调用本地Service方法完成项目信息的保存

@RestController
public class ProjectProviderHandler {@Autowiredprivate ProjectService projectService;@RequestMapping("/save/project/vo/remote")public ResultEntity<String> saveProjectVORemote(@RequestBody ProjectVO projectVO, @RequestParam("memberId") Integer memberId) {try {// 调用“本地”Service执行保存projectService.saveProject(projectVO, memberId);return ResultEntity.successWithoutData();} catch (Exception e) {e.printStackTrace();return ResultEntity.failed(e.getMessage());}}}

6.2、Service代码

  • 分为如下步骤:

    • 保存ProjectPO对象
    • 保存项目分类信息
    • 保存项目标签信息
    • 保存项目发起人信息
    • 保存项目回报信息
    • 保存项目确认信息

@Transactional(readOnly = true)
@Service
public class ProjectServiceImpl implements ProjectService {@Autowiredprivate ReturnPOMapper returnPOMapper;@Autowiredprivate MemberConfirmInfoPOMapper memberConfirmInfoPOMapper;@Autowiredprivate MemberLaunchInfoPOMapper memberLaunchInfoPOMapper;@Autowiredprivate ProjectPOMapper projectPOMapper;@Autowiredprivate ProjectItemPicPOMapper projectItemPicPOMapper;@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)@Overridepublic void saveProject(ProjectVO projectVO, Integer memberId) {// 一、保存ProjectPO对象// 1.创建空的ProjectPO对象ProjectPO projectPO = new ProjectPO();// 2.把projectVO中的属性复制到projectPO中BeanUtils.copyProperties(projectVO, projectPO);// 修复bug:把memberId设置到projectPO中projectPO.setMemberid(memberId);// 修复bug:生成创建时间存入String createdate = new SimpleDateFormat("yyyy-MM-dd").format(new Date());projectPO.setCreatedate(createdate);// 修复bug:status设置成0,表示即将开始projectPO.setStatus(0);// 3.保存projectPO// 为了能够获取到projectPO保存后的自增主键,需要到ProjectPOMapper.xml文件中进行相关设置// <insert id="insertSelective" useGeneratedKeys="true" keyProperty="id" ……projectPOMapper.insertSelective(projectPO);// 4.从projectPO对象这里获取自增主键Integer projectId = projectPO.getId();// 二、保存项目、分类的关联关系信息// 1.从ProjectVO对象中获取typeIdListList<Integer> typeIdList = projectVO.getTypeIdList();projectPOMapper.insertTypeRelationship(typeIdList, projectId);// 三、保存项目、标签的关联关系信息List<Integer> tagIdList = projectVO.getTagIdList();projectPOMapper.insertTagRelationship(tagIdList, projectId);// 四、保存项目中详情图片路径信息List<String> detailPicturePathList = projectVO.getDetailPicturePathList();projectItemPicPOMapper.insertPathList(projectId, detailPicturePathList);// 五、保存项目发起人信息MemberLauchInfoVO memberLauchInfoVO = projectVO.getMemberLauchInfoVO();MemberLaunchInfoPO memberLaunchInfoPO = new MemberLaunchInfoPO();BeanUtils.copyProperties(memberLauchInfoVO, memberLaunchInfoPO);memberLaunchInfoPO.setMemberid(memberId);memberLaunchInfoPOMapper.insert(memberLaunchInfoPO);// 六、保存项目回报信息List<ReturnVO> returnVOList = projectVO.getReturnVOList();List<ReturnPO> returnPOList = new ArrayList<>();for (ReturnVO returnVO : returnVOList) {ReturnPO returnPO = new ReturnPO();BeanUtils.copyProperties(returnVO, returnPO);returnPOList.add(returnPO);}returnPOMapper.insertReturnPOBatch(returnPOList, projectId);// 七、保存项目确认信息MemberConfirmInfoVO memberConfirmInfoVO = projectVO.getMemberConfirmInfoVO();MemberConfirmInfoPO memberConfirmInfoPO = new MemberConfirmInfoPO();BeanUtils.copyProperties(memberConfirmInfoVO, memberConfirmInfoPO);memberConfirmInfoPO.setMemberid(memberId);memberConfirmInfoPOMapper.insert(memberConfirmInfoPO);}}

6.3、获取自增主键

  • 设置插入数据时,获取插入数据的自增主键

    • useGeneratedKeys="true":获取自增主键
    • keyProperty="id"PO类中主键属性名为id

<insert id="insertSelective" useGeneratedKeys="true" keyProperty="id" parameterType="com.atguigu.crowd.entity.po.ProjectPO" >insert into t_project<trim prefix="(" suffix=")" suffixOverrides="," ><if test="id != null" >id,</if><if test="projectName != null" >project_name,</if><if test="projectDescription != null" >project_description,</if><if test="money != null" >money,</if><if test="day != null" >day,</if><if test="status != null" >status,</if><if test="deploydate != null" >deploydate,</if><if test="supportmoney != null" >supportmoney,</if><if test="supporter != null" >supporter,</if><if test="completion != null" >completion,</if><if test="memberid != null" >memberid,</if><if test="createdate != null" >createdate,</if><if test="follower != null" >follower,</if><if test="headerPicturePath != null" >header_picture_path,</if></trim><trim prefix="values (" suffix=")" suffixOverrides="," ><if test="id != null" >#{id,jdbcType=INTEGER},</if><if test="projectName != null" >#{projectName,jdbcType=VARCHAR},</if><if test="projectDescription != null" >#{projectDescription,jdbcType=VARCHAR},</if><if test="money != null" >#{money,jdbcType=BIGINT},</if><if test="day != null" >#{day,jdbcType=INTEGER},</if><if test="status != null" >#{status,jdbcType=INTEGER},</if><if test="deploydate != null" >#{deploydate,jdbcType=VARCHAR},</if><if test="supportmoney != null" >#{supportmoney,jdbcType=BIGINT},</if><if test="supporter != null" >#{supporter,jdbcType=INTEGER},</if><if test="completion != null" >#{completion,jdbcType=INTEGER},</if><if test="memberid != null" >#{memberid,jdbcType=INTEGER},</if><if test="createdate != null" >#{createdate,jdbcType=VARCHAR},</if><if test="follower != null" >#{follower,jdbcType=INTEGER},</if><if test="headerPicturePath != null" >#{headerPicturePath,jdbcType=VARCHAR},</if></trim>
</insert>

6.4、保存项目分类信息

6.4.1、项目分类表

  • 存储项目与分类信息之间的关联关系

6.4.2、Mapper接口

void insertTypeRelationship(@Param("typeIdList") List<Integer> typeIdList, @Param("projectId") Integer projectId);

6.4.3、Mapper映射文件

<!-- void insertTypeRelationship(@Param("typeIdList") List<Integer> typeIdList, @Param("projectId") Integer projectId);-->
<insert id="insertTypeRelationship">insert into t_project_type(`projectid`,`typeid`) values<foreach collection="typeIdList" item="typeId" separator=",">(#{projectId},#{typeId})</foreach>
</insert>

6.5、保存项目标签信息

6.5.1、项目标签表

  • 存储项目与标签信息之间的关联关系

6.5.2、Mapper接口

void insertTagRelationship(@Param("tagIdList") List<Integer> tagIdList, @Param("projectId") Integer projectId);

6.5.3、Mapper映射文件

<!-- void insertTagRelationship(@Param("tagIdList") List<Integer> tagIdList, @Param("projectId") Integer projectId);-->
<insert id="insertTagRelationship">insert into `t_project_tag`(`projectid`,`tagid`) values<foreach collection="tagIdList" item="tagId" separator=",">(#{projectId},#{tagId})</foreach>
</insert>

6.6、保存项目详情图片

6.6.1、项目详情图片表

  • 存储项目详情图片的网络路径

6.6.2、Mapper接口

void insertPathList(@Param("projectId") Integer projectId, @Param("detailPicturePathList") List<String> detailPicturePathList);

6.6.3、Mapper映射文件

<!-- void insertPathList(@Param("projectId") Integer projectId, @Param("detailPicturePathList") List<String> detailPicturePathList);-->
<insert id="insertPathList">insert into t_project_item_pic (projectid, item_pic_path)values<foreach collection="detailPicturePathList" item="detailPath" separator=",">(#{projectId},#{detailPath})</foreach>
</insert>

6.7、保存项目回报信息

6.7.1、项目回报表

  • 存储项目的回报信息

6.7.2、Mapper接口

void insertReturnPOBatch(@Param("returnPOList") List<ReturnPO> returnPOList, @Param("projectId") Integer projectId);

6.7.3、Mapper映射文件

<!--
void insertReturnPOBatch(
@Param("returnPOList") List<ReturnPO> returnPOList,
@Param("projectId") Integer projectId);-->
<insert id="insertReturnPOBatch">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>

6.8、测试

  • 提交数据成功~

  • t_projectProject详细信息

  • t_project_type:项目与分类的关联关系

  • t_project_tag:项目与标签的关联关系

  • t_project_item_pic:项目详情图片的路径

  • t_member_launch_info:发起人信息

  • t_return:回报信息

  • t_member_confirm_info:确认信息

  • OSS上传图片成功

6.9、遇到的问题

  • 可以看到,money字段的值并没有保存成功,我们需要将money字段的类型从Long修改为Integer

  • Mybatis逆向生成时,将ProjectPO类的money字段的类型设置为Long,而我们自己写的ProjectVO类的money字段的类型为Integer

  • BeanUtils.copyProperties拷贝属性值时,貌似会拷贝失败???反正请求参数中的money字段值能正常注入

day15【前台】项目发布相关推荐

  1. Android开源项目发布jCenter

    最近有这方面需要,所以研究了一下如何将自己的工程项目发布到jCenter上去.方法有很多,可以是Bintray,maven,jitPack.io等等. 本次发布使用的Bintray,所以稍后先从如何使 ...

  2. python生日贺卡制作以及细节问题的解决最后把python项目发布为exe可执行程序过程

    python生日贺卡制作以及细节问题的解决最后把python项目发布为exe可执行程序过程 参考文章: (1)python生日贺卡制作以及细节问题的解决最后把python项目发布为exe可执行程序过程 ...

  3. 项目发布: error CS0103: 当前上下文中不存在名称“*****”

    为什么80%的码农都做不了架构师?>>> 项目发布,发布不出来,而且编译,发布过程中vs也不报错,也不提示错误. 在错误窗口忽闪一个错误提示之后,输出窗口有西边的提示: error ...

  4. Tomcat如何将项目发布到webapps目录下

    Eclipse默认发布项目时,Javaweb项目会被发布到Eclipse工作空间下,而不再Tomcat下的webapps下. 默认时,完整路径: workspace路径\.metadata\.plug ...

  5. java发布后功能不能用,急项目发布后java写的打印功能失效了-求解解决方法

    当前位置:我的异常网» J2EE » 急项目发布后java写的打印功能失效了-求解解决方法 急项目发布后java写的打印功能失效了-求解解决方法 www.myexceptions.net  网友分享于 ...

  6. 【狂人小白】如何将Java项目发布到Maven中

    将Java项目发布到Maven中 如何通过Sonatype进行发布Maven操作,并且能够在http://search.maven.org 中搜索到 关键步骤: 1. 申请OSS账号 2. 通过gpg ...

  7. .NET项目发布网站具体步骤和注意事项

    ASP.NET项目发布网站具体步骤 1.     在解决方案管理器中右键项目名称,点击"发布网站",如图 2.     在发布窗口中选择"文件系统",然后选择文 ...

  8. 读完 Vue 发布源码,小姐姐回答了 leader 的提问,并优化了项目发布流程~

    大家好,我是若川.这是 源码共读 第三期活动,纪年小姐姐的第三次投稿.纪年小姐姐学习完优化了自己的项目发布流程,而且回答了leader对她的提问,来看看她的思考和实践. 第三期是 Vue 3.2 发布 ...

  9. maven项目发布到tomcat里lib包没有发布的问题

    背景:使用maven搭建一套开发环境 出现的问题: 编译之后在tomcat中web工程下面的lib包没有将jar包打入.截图如下: 解决方法: 右键项目,选择properties,然后选 在使用ecl ...

最新文章

  1. Find Code for Research Papers
  2. 802.11(wi-fi)的PHY层(编码与调制方法)
  3. 五十五、Java单元测试类Junit
  4. android旋转动画和平移动画具体解释,补充说一下假设制作gif动画放到csdn博客上...
  5. JFinal model简单包装,版本2
  6. java实现的小程序_Java实现 微信小程序 + 消息推送
  7. centos8 安装mysql_Centos 离线安装mysql8(以及在线安装)
  8. mysql 工具 国产_推荐一款国产化比较好用的数据可视化工具(BI工具)
  9. 20181102_WCF简单双工
  10. 微设计(www.weidesigner.com)介绍系列文章(三)
  11. linux扫描后台地址,Linux如何查看和控制进程
  12. 我的世界服务器物品图标闪,我的世界中国版用资源包修改闪烁标的方法分享
  13. Redis3.x 源码安装
  14. 解决input输入中文时,拼音在输入框内会触发input事件的问题
  15. 光明顶短信支付:BASE64编码
  16. 【Android】App开发-动画效果篇
  17. 开源魔兽世界私服搭建
  18. wps批量将文档括号内的字符串修改颜色
  19. 51单片机模拟串口发送接收数据(不使用SBUF)
  20. MXC智能物联竞价-基于AI的物联网通证化协议分析

热门文章

  1. 2021年中国助听器市场趋势报告、技术动态创新及2027年市场预测
  2. 第14章 火柴人的无尽冒险(《C和C++游戏趣味编程》配套教学视频)
  3. SQL注入学习part04:(结合sqli-libs学习:31-40关)
  4. 淘宝特价版招聘:年薪50万,35岁以上优先;1900万:一线城市财富自由的入门级门槛;鸿蒙OS成武汉大学专业选修课 | 极客头条...
  5. 15行代码抓取兰亭序全文单字高清字帖
  6. 漫画:应用程序被拖慢?罪魁祸首是 Log4j!
  7. 中科院发布“木兰”处理意见;互联网公司合力应对新型肺炎;PHP 7.4.2 发布| 极客头条...
  8. 朋友圈发送照片泄露位置?微信:P 完再发!
  9. 多种方式创建 Entity Framework Core 上下文
  10. 刷爆抖音,评分9.7!这本Python书太酷了!程序员:太爱!