天天看点

day15【前台】项目发布day15【前台】项目发布

day15【前台】项目发布

1、OSS

1.1、开通OSS服务

  • 进入控制台,选择【对象存储OSS】
day15【前台】项目发布day15【前台】项目发布
  • 立即开通
day15【前台】项目发布day15【前台】项目发布
  • 同意协议
day15【前台】项目发布day15【前台】项目发布
  • 开通完成后,前往控制台创建

    Bucket

day15【前台】项目发布day15【前台】项目发布

1.2、创建Bucket

  • 创建

    Bucket

day15【前台】项目发布day15【前台】项目发布
  • Bucket

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

1.3、页面上传文件

  • 新建目录用于存储文件
day15【前台】项目发布day15【前台】项目发布
  • 创建多级目录
day15【前台】项目发布day15【前台】项目发布
  • 选择上传文件
day15【前台】项目发布day15【前台】项目发布
  • 上传本地图片至阿里云

    OSS

day15【前台】项目发布day15【前台】项目发布
day15【前台】项目发布day15【前台】项目发布
  • 图片的地址:

    Bucket

    域名+图片路径
day15【前台】项目发布day15【前台】项目发布

1.4、创建子用户AccessKey

  • 一般我们不使用主账户(登录账户)来调用 API 接口,因为主账户的权限太大,一旦 key secret 泄露,就玩完。。。
  • 一般创建子账户来调用 OSS API 接口,点击用户头像,选择 AccessKey管理
day15【前台】项目发布day15【前台】项目发布
  • 选择开始使用子用户 AccessKey
day15【前台】项目发布day15【前台】项目发布
  • 设计登录名称和显示名称,选择编程访问(自己编写的程序调用或 PicGo 等第三方程序调用)
day15【前台】项目发布day15【前台】项目发布
  • Key Secret

    貌似仅此一次机会可以查看,赶紧保存下来
day15【前台】项目发布day15【前台】项目发布

1.5、子用户AccessKey授权

  • 给创建的子用户添加权限
day15【前台】项目发布day15【前台】项目发布
  • 添加 OSS 服务:AliyunOSSFullAccess ,FullAccess 表示可读可写
day15【前台】项目发布day15【前台】项目发布

1.6、project-consumer环境

1.6.1、引入依赖

  • project-consumer

    工程中引入所需的依赖
day15【前台】项目发布day15【前台】项目发布
<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、创建主启动类

day15【前台】项目发布day15【前台】项目发布
//启用Feign客户端功能
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class CrowdMainClass {
	
	public static void main(String[] args) {
		SpringApplication.run(CrowdMainClass.class, args);
	}

}
           

1.6.3、准备OSSProperties类

  • 创建

    OSSProperties

    配置类
day15【前台】项目发布day15【前台】项目发布
@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

    基本配置
day15【前台】项目发布day15【前台】项目发布
server:
  port: 5000
  
spring:
  application:
    name: atguigu-crowd-project
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
  redis:
    host: 192.168.152.129
  session:
    store-type: redis

eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka
      
aliyun:
  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-domain

    end-point

    去阿里云控制台复制,

    bucket-name

    就是

    heygo

    (当初创建

    Bucket

    时取的名字)
day15【前台】项目发布day15【前台】项目发布

1.7、上传文件工具方法

1.7.1、引入依赖

  • util

    工程引入阿里云

    OSS

    服务所需的依赖
day15【前台】项目发布day15【前台】项目发布
<!-- OSS客户端SDK -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.5.0</version>
</dependency>
           

1.7.2、创建工具方法

  • 在工具类中添加如下方法
day15【前台】项目发布day15【前台】项目发布
/**
 * 专门负责上传文件到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、创建测试类

day15【前台】项目发布day15【前台】项目发布
@RunWith(SpringRunner.class)
@SpringBootTest
public class OSSTest {

	@Autowired
	private OSSProperties oSSProperties;

	@Test
	public 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());
		
	}
	
}
           
  • 图片放置路径:工程根目录下
day15【前台】项目发布day15【前台】项目发布

1.8.2、测试结果

  • 控制台打印
SUCCESS
           
  • OSS

    上传图片成功
day15【前台】项目发布day15【前台】项目发布

2、跳转到发起项目页面

2.1、整体思路

day15【前台】项目发布day15【前台】项目发布

2.2、解决Zuul相关问题

2.2.1、序列化

  • SpringSession

    存储对象至

    Redis

    中,要求该类必须是可序列化的,否则会抛异常
day15【前台】项目发布day15【前台】项目发布
@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

    ,却取不出来,提示

    session

    null

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
           
day15【前台】项目发布day15【前台】项目发布
2、问题分析
  • auth-consumer

    所属域名为

    http://localhost:4000

    zuul

    所属域名为

    http://localhost:80

    ,他他俩属于不同的域名, 浏览器工作时不会使用相同的

    Cookie

    ,自然

    session

    就不一样(不共享)咯
3、解决方法
  • 以后重定向的地址都按照通过

    Zuul

    访问的方式写地址
day15【前台】项目发布day15【前台】项目发布
@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

    才能保持一致
day15【前台】项目发布day15【前台】项目发布
day15【前台】项目发布day15【前台】项目发布
3、举例
  • 【退出系统】按钮的超链接
day15【前台】项目发布day15【前台】项目发布
  • 退出成功后,页面重定向至

    http://www.crowd.com

day15【前台】项目发布day15【前台】项目发布

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

day15【前台】项目发布day15【前台】项目发布

2.3、我的众筹

2.3.1、修改超链接

  • 修改【我的众筹】超链接
day15【前台】项目发布day15【前台】项目发布
<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

    页面
day15【前台】项目发布day15【前台】项目发布
@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {
	
	@Override
	public 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、众筹页面

  • 添加众筹页面,并添加用户信息的回显
day15【前台】项目发布day15【前台】项目发布

2.3.4、测试

  • 点击【我的众筹】,正常跳转
day15【前台】项目发布day15【前台】项目发布

3、发起项目

3.1、整体思路

day15【前台】项目发布day15【前台】项目发布

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、逆向工程配置文件

  • 注意:不生成中间表对应的实体类
day15【前台】项目发布day15【前台】项目发布
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
			  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
			  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
	<!-- mybatis-generator:generate -->
	<context id="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

day15【前台】项目发布day15【前台】项目发布

3.3.3、资源归位

1、实体类
day15【前台】项目发布day15【前台】项目发布
2、Mapper接口
day15【前台】项目发布day15【前台】项目发布
3、Mapper.xml映射文件
day15【前台】项目发布day15【前台】项目发布

3.4、创建组件

3.4.1、ProjectHandler

day15【前台】项目发布day15【前台】项目发布

3.4.2、ProjectService

day15【前台】项目发布day15【前台】项目发布

3.5、创建VO类

day15【前台】项目发布day15【前台】项目发布
  • 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

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

3.7、跳转至发起项目页面

3.7.1、点击发起众筹

  • 修改【发起众筹】按钮的跳转地址
    • window.location.href = "http://www.crowd.com/project/agree/protocol/page";

      表示浏览器的跳转地址
    • 一定要经过

      zuul

      网关再进行跳转,否则会出现

      session

      不一致的问题
day15【前台】项目发布day15【前台】项目发布
<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

day15【前台】项目发布day15【前台】项目发布
@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {
	
	@Override
	public 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

    工程中,创建用户协议页面、发起项目页面
day15【前台】项目发布day15【前台】项目发布

3.7.4、测试

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

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

    域中,以便后续收集完成,保存至数据库中
day15【前台】项目发布day15【前台】项目发布

3.9.2、前端表单提交代码

  • 标签信息需要单独采集,最后以隐藏域的形式追加在表单数据后面
day15【前台】项目发布day15【前台】项目发布
// 点击下一步按钮提交表单
$("#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

      域中,并转发至回报页面
day15【前台】项目发布day15【前台】项目发布
@Controller
public class ProjectConsumerHandler {
	
	@Autowired
	private 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

day15【前台】项目发布day15【前台】项目发布
@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {
	
	@Override
	public 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、添加回报页面

day15【前台】项目发布day15【前台】项目发布

3.11、测试

  • 图片能上传至阿里与

    OSS

day15【前台】项目发布day15【前台】项目发布
  • Redis

    中的

    session

day15【前台】项目发布day15【前台】项目发布

4、提交回报信息

4.1、整体思路

day15【前台】项目发布day15【前台】项目发布

4.2、Ajax上传图片

4.2.1、前端Ajax请求

  • Ajax

    形式提交图片,并通过响应信息获取图片的网络位置
  • "url":"[[@{/project/create/upload/return/picture.json}]]"

    :在页面地址中需要带上

    zuul

    网关的路由规则,即域名+微服务路径

    project

day15【前台】项目发布day15【前台】项目发布
// 在文件上传框的值改变事件响应函数中预览并上传图片
$("[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

    ,并返回上传的结果
day15【前台】项目发布day15【前台】项目发布
// 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

    请求
day15【前台】项目发布day15【前台】项目发布
// 声明序号保存表格中数据的序号
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

    对象
day15【前台】项目发布day15【前台】项目发布
@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是否为null
		if(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域,以确保新的数据最终能够存入Redis
		session.setAttribute(CrowdConstant.ATTR_NAME_TEMPLE_PROJECT, projectVO);
		
		// 9.所有操作成功完成返回成功
		return ResultEntity.successWithoutData();
	} catch (Exception e) {
		e.printStackTrace();
		
		return ResultEntity.failed(e.getMessage());
	}
	
}
           

4.4、测试

  • 回报信息提交成功
day15【前台】项目发布day15【前台】项目发布
  • 阿里云

    OSS

    上传图片成功
day15【前台】项目发布day15【前台】项目发布

5、提交确认信息

5.1、创建页面

  • project-confirm.html

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

    :成功页面
day15【前台】项目发布day15【前台】项目发布

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

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

5.3、添加view-controller

day15【前台】项目发布day15【前台】项目发布
@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {
	
	@Override
	public 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

    实体类属性名对应
day15【前台】项目发布day15【前台】项目发布
<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、提交按钮

  • 点击【提交】按钮提交表单
day15【前台】项目发布day15【前台】项目发布
<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

    对象,并重定向至成功页面
day15【前台】项目发布day15【前台】项目发布
@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为null
    if(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

    远程调用接口中声明上述方法
day15【前台】项目发布day15【前台】项目发布
@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

    方法完成项目信息的保存
day15【前台】项目发布day15【前台】项目发布
@RestController
public class ProjectProviderHandler {
	
	@Autowired
	private 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

      对象
    • 保存项目分类信息
    • 保存项目标签信息
    • 保存项目发起人信息
    • 保存项目回报信息
    • 保存项目确认信息
day15【前台】项目发布day15【前台】项目发布
@Transactional(readOnly = true)
@Service
public class ProjectServiceImpl implements ProjectService {
	
	@Autowired
	private ReturnPOMapper returnPOMapper;
	
	@Autowired
	private MemberConfirmInfoPOMapper memberConfirmInfoPOMapper;
	
	@Autowired
	private MemberLaunchInfoPOMapper memberLaunchInfoPOMapper;
	
	@Autowired
	private ProjectPOMapper projectPOMapper;
	
	@Autowired
	private ProjectItemPicPOMapper projectItemPicPOMapper;

	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
	@Override
	public 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对象中获取typeIdList
		List<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

day15【前台】项目发布day15【前台】项目发布
<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、项目分类表

  • 存储项目与分类信息之间的关联关系
day15【前台】项目发布day15【前台】项目发布

6.4.2、Mapper接口

day15【前台】项目发布day15【前台】项目发布
void insertTypeRelationship(
    @Param("typeIdList") List<Integer> typeIdList, 
    @Param("projectId") Integer projectId);
           

6.4.3、Mapper映射文件

day15【前台】项目发布day15【前台】项目发布
<!-- 
	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、项目标签表

  • 存储项目与标签信息之间的关联关系
day15【前台】项目发布day15【前台】项目发布

6.5.2、Mapper接口

day15【前台】项目发布day15【前台】项目发布
void insertTagRelationship(
    @Param("tagIdList") List<Integer> tagIdList, 
    @Param("projectId") Integer projectId);
           

6.5.3、Mapper映射文件

day15【前台】项目发布day15【前台】项目发布
<!-- 
	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、项目详情图片表

  • 存储项目详情图片的网络路径
day15【前台】项目发布day15【前台】项目发布

6.6.2、Mapper接口

day15【前台】项目发布day15【前台】项目发布
void insertPathList(
    @Param("projectId") Integer projectId, 
    @Param("detailPicturePathList") List<String> detailPicturePathList);
           

6.6.3、Mapper映射文件

day15【前台】项目发布day15【前台】项目发布
<!-- 
	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、项目回报表

  • 存储项目的回报信息
day15【前台】项目发布day15【前台】项目发布

6.7.2、Mapper接口

day15【前台】项目发布day15【前台】项目发布
void insertReturnPOBatch(
    @Param("returnPOList") List<ReturnPO> returnPOList, 
    @Param("projectId") Integer projectId);
           

6.7.3、Mapper映射文件

day15【前台】项目发布day15【前台】项目发布
<!-- 
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、测试

  • 提交数据成功~
day15【前台】项目发布day15【前台】项目发布
  • t_project

    Project

    详细信息
day15【前台】项目发布day15【前台】项目发布
  • t_project_type

    :项目与分类的关联关系
day15【前台】项目发布day15【前台】项目发布
  • t_project_tag

    :项目与标签的关联关系
day15【前台】项目发布day15【前台】项目发布
  • t_project_item_pic

    :项目详情图片的路径
day15【前台】项目发布day15【前台】项目发布
  • t_member_launch_info

    :发起人信息
day15【前台】项目发布day15【前台】项目发布
  • t_return

    :回报信息
day15【前台】项目发布day15【前台】项目发布
  • t_member_confirm_info

    :确认信息
day15【前台】项目发布day15【前台】项目发布
  • OSS

    上传图片成功
day15【前台】项目发布day15【前台】项目发布

6.9、遇到的问题

  • 可以看到,

    money

    字段的值并没有保存成功,我们需要将

    money

    字段的类型从

    Long

    修改为

    Integer

day15【前台】项目发布day15【前台】项目发布
  • Mybatis

    逆向生成时,将

    ProjectPO

    类的

    money

    字段的类型设置为

    Long

    ,而我们自己写的

    ProjectVO

    类的

    money

    字段的类型为

    Integer

day15【前台】项目发布day15【前台】项目发布
day15【前台】项目发布day15【前台】项目发布
  • BeanUtils.copyProperties

    拷贝属性值时,貌似会拷贝失败???反正请求参数中的

    money

    字段值能正常注入
day15【前台】项目发布day15【前台】项目发布
day15【前台】项目发布day15【前台】项目发布

继续阅读