黑马头条项目之项目设计及基础搭建

一、概述
工程基于Spring-boot 2.1.5.RELEASE 版本构建,工程父项目为heima-leadnews,并通过继承方式集成Spring-boot。
父项目下分5个公共子项目:
1.heima-leadnews-common : 是整个工程的配置核心,包括所有集成三方框架的配置定义,比如redis、kafka等。除此之外还包括项目每个模块及整个项目的常量定义;
2.heima-leadnews-model :项目中用到的Dto、Pojo、Mapper、Enums定义工程;
3.heima-leadnews-utils : 工程公用工具类项目,包含加密/解密、Date、JSON等工具类;
4.heima-leadnew-apis : 整个项目微服务暴露的接口的定义项目,按每个模块进行子包拆分;
5.heima-leadnew-tests : 用于存放工程中通用的测试用例;
二、项目需求
黑马头条项目 一 项目设计及基础搭建-编程知识网三、技术架构
黑马头条项目 一 项目设计及基础搭建-编程知识网本章实现功能如下:

1.基础工程的导入以及熟悉

  • 微服务实现如下:
  • heima-leadnews-article:用于实现APP端文章的获取与更新;
  • heima-leadnews-behavior:用于实现APP端各类行为数据的上传服务;

一、基础工程的创建与导入

  • 首先创建父工程
    黑马头条项目 一 项目设计及基础搭建-编程知识网黑马头条项目 一 项目设计及基础搭建-编程知识网
  • 依次导入四个子工程(资源在文章最后)
    黑马头条项目 一 项目设计及基础搭建-编程知识网
  • 数据库的设计(sql文件导入资源在文章最后)
    黑马头条项目 一 项目设计及基础搭建-编程知识网
  • mysql配置
    在maven_test.properties修改你的相应的账户密码(密码使用了反向加密,反着写)
mysql.core.jdbc.url=jdbc:mysql://localhost:3306/heima-leadnews?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
mysql.core.jdbc.username=root
mysql.core.jdbc.password=toormysql.core.jdbc.driver=com.mysql.jdbc.Driver
mysql.core.root.mapper=mappers
mysql.core.aliases.package=com.heima.model.**
mysql.core.tx.scan.package=execution(* com.heima..service.*.*(..))

二、文章的更新功能实现

  • 功能需求分析
    黑马头条项目 一 项目设计及基础搭建-编程知识网分析:
情况分有
1.是否登录
2.上拉、下拉
3.加载更多,加载更新
  • 相关表结构分析
    ap_article文章信息表

文章信息表,存储已发布的文章

字段名称 类型 说明
id int(11) 主键
title varchar(50) 标题
author_id int(11) 文章作者的ID
author_name varchar(20) 作者昵称
channel_id int(10) 文章所属频道ID
channel_name varchar(10) 频道名称
layout tinyint(1) 文章布局 0 无图文章 1 单图文章 2 多图文章
flag tinyint(3) 文章标记 0 普通文章 1 热点文章 2 置顶文章 3 精品文章 4 大V 文章
images varchar(1000) 文章图片 多张逗号分隔
labels varchar(500) 文章标签最多3个 逗号分隔
likes int(5) 点赞数量
collection int(5) 收藏数量
comment int(5) 评论数量
views int(5) 阅读数量
province_id int(11) 省市
city_id int(11) 市区
county_id int(11) 区县
created_time datetime 创建时间
publish_time datetime 发布时间
sync_status tinyint(1) 同步状态
origin tinyint(1) 来源

ap_user_article_list APP用户文章列表

字段名称 类型 说明
id int(11) 主键
user_id int(11) 用户ID
channel_id int(11) 频道ID
article_id int(11) 文章ID
is_show tinyint(1) 是否展示
recommend_time datetime 推荐时间
is_read tinyint(1) 是否阅读
strategy_id int(5) 推荐算法

ap_show_behaviorAPP文章展现行为表

字段 类型 描述
id int(11) 主键
entry_id int(11) 实体ID
article_id int(11) 文章ID
is_click tinyint(1) 是否点击
show_time datetime 文章加载时间
created_time datetime 登录时间

ap_behavior_entry app行为实体表

APP行为实体表,一个行为实体可能是用户或者设备,或者其它

字段 类型 描述
id int(11) 主键
type tinyint(1) 实体类型 0终端设备 1用户
entry_id int(11) 实体ID
created_time datetime 创建时间
burst varchar(40) 分片
  • 接口定义

接口定义分析

由需求分析可知用户在首页的是可能触发的行为有加载文章列表,刷新(上拉刷新、下拉刷新)等动作,这也就是我们后端需要对应的几个数据接口,其中还包含一个隐含的用户行为接口用户记录用户是否阅读某一篇文章的行为接口,则我们可以分析出后端需要的接口有:

(1)load接口

load接口,分两种情况,一个是登录,一个是未登录,加载多条数据(有条数的限制,size),用户可以选择频道进行数据的切换

  • 登录,从后台获取用户信息,作为条件查询

  • 未登录,直接加载默认数据即可。

(2)load_more接口 && load_new 接口

当用户进行刷新是,在我们的系统中定义了两种操作,第一种是上拉刷新也就是load_more,第二种是下拉刷新load_new接口,这两个接口的区别在于加载的内容的时间不同

用户进入系统的时间TimeA浏览了一会首次加载的数据之后到了TimeB时间,这时候如果用户继续上拉看后面的内容则我们调用load_more接口并把TimeB时间传递到后端,后端根据TimeB时间查找当前时间之前发布的内容;

用户下拉刷新则说明用户需要当前最新的内容则调用load_new接口,将TimeA时间传到后端后端查找TimeA时间之后发布的内容。其请求参数接口设计和load接口相同。

(4)behavior 行为接口

记录用户操作行为

  • 一系列相关功能对应一个子项目

    在原有的工程中创建一个普通的maven工程模块,选择其作为heima-leadnews作为父工程,并给当前模块命名为heima-leadnews-article 配置我们需要的jar的maven坐标,以及我们项目中模块的项目依赖 注意我们的数据库相关的实体以及Mapper接口和配置文件都是存放在Model 模块。

    定义pom文件中的依赖信息

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>heima-leadnews</artifactId><groupId>com.heima</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>heima-leadnews-article</artifactId><dependencies><!-- 引入依赖模块 --><dependency><groupId>com.heima</groupId><artifactId>heima-leadnews-model</artifactId></dependency><dependency><groupId>com.heima</groupId><artifactId>heima-leadnews-common</artifactId></dependency><dependency><groupId>com.heima</groupId><artifactId>heima-leadnews-apis</artifactId></dependency><!-- Spring boot starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!-- 排除默认的logback日志,使用log4j--><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion><exclusion><groupId>ch.qos.logback</groupId><artifactId>logback-access</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-cbor</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId></dependency></dependencies>
</project>

Article(文章)模块工程结构

注意这里可以只创建基础的包结构即可,后续会讲解每个具体的类的作用以及实现;此处可能你又疑问为什么我们的控制器加了个v1,这里我们的做法是为了兼容不同的终端版本所设立的版本号
黑马头条项目 一 项目设计及基础搭建-编程知识网

项目根路径添加文件:maven_dev.properties

# log4j
log.level=DEBUG
log.pattern=%d{DEFAULT}^|%sn^|%level^|%t^|%c^|%M^|%msg%n

项目根路径添加文件:maven_prod.properties

# log4j
log.level=DEBUG
log.pattern=%d{DEFAULT}^|%sn^|%level^|%t^|%c^|%M^|%msg%n

项目根路径添加文件:maven_test.properties

#log4j
log.level=DEBUG
log.pattern=%d{DEFAULT}^|%sn^|%level^|%t^|%c^|%M^|%msg%n

在resource目录下创建application.properties和log4j2.xml

application.properties

server.port=${port.article}
spring.application.name=${sn.article}

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration><properties><property name="CONSOLE_PATTERN">${log.pattern}</property><property name="FILE_NAME">${project.build.finalName}</property></properties><!--先定义所有的appender--><appenders><!--这个输出控制台的配置--><console name="Console" target="SYSTEM_OUT"><!--输出日志的格式--><PatternLayout pattern="${CONSOLE_PATTERN}"/></console><!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--><RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/${FILE_NAME}.log"filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd}-%i.log"><PatternLayout pattern="${CONSOLE_PATTERN}"/><Policies><TimeBasedTriggeringPolicy/><SizeBasedTriggeringPolicy size="100 MB"/></Policies></RollingFile></appenders><loggers><!--过滤掉spring和mybatis的一些无用的DEBUG信息--><logger name="org.springframework" level="INFO"></logger><logger name="org.mybatis" level="INFO"></logger><logger name="org.apache.http" level="INFO"></logger><logger name="org.apache.kafka" level="INFO"></logger><logger name="com.netflix.discovery" level="INFO"></logger><logger name="org.hibernate" level="INFO"></logger><root level="${log.level}"><appender-ref ref="Console"/><appender-ref ref="RollingFileInfo"/></root></loggers>
</configuration>

mysql初始化扫描配置类com.heima.article.config.MysqlConfig

@Configuration
@ComponentScan("com.heima.common.mysql.core")
public class MysqlConfig {}

article服务功能开发

(0)定义dto 数据传输对象

@Data
public class ArticleHomeDto {// 省市Integer provinceId;// 市区Integer cityId;// 区县Integer countyId;// 最大时间Date maxBehotTime;// 最小时间Date minBehotTime;// 分页sizeInteger size;// 数据范围,比如频道IDString tag;}

(1)接口定义,在apis模块中我们建立包com.heima.article.apis并定义接口ArticleHomeControllerApi

/*** 首頁文章*/
public interface ArticleHomeControllerApi {/*** 加載首頁文章* @param dto 封装参数对象* @return 文章列表数据*/ResponseResult load(ArticleHomeDto dto);/*** 加载更多* @param dto 封装参数对象* @return 文章列表数据*/ResponseResult loadMore(ArticleHomeDto dto);/*** 加载最新的数据* @param dto 封装参数对象* @return 文章列表*/ResponseResult loadNew(ArticleHomeDto dto);}

(2)在定义完数据接口之后,我们需要做的就是去article模块定义我们的控制器, 如果你是拷贝的下面代码你可能发现你的代码中ArticleIndexService没有定义,报错了这里不用慌我们后面就是service层的编写,后面service到dao层也是同样的

@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController implements ArticleHomeControllerApi {@Autowiredprivate AppArticleService appArticleService;@Override@GetMapping("/load")public ResponseResult load(ArticleHomeDto dto) {return appArticleService.load( ArticleConstans.LOADTYPE_LOAD_MORE, dto);}@Override@GetMapping("/loadmore")public ResponseResult loadMore(ArticleHomeDto dto) {return appArticleService.load( ArticleConstans.LOADTYPE_LOAD_MORE, dto);}@Override@GetMapping("/loadnew")public ResponseResult loadNew(ArticleHomeDto dto) {return appArticleService.load( ArticleConstans.LOADTYPE_LOAD_NEW, dto);}}

定义常量com.heima.common.article.constans.ArticleConstans

public class ArticleConstans{public static final Short LOADTYPE_LOAD_MORE = 1;//加载更多public static final Short LOADTYPE_LOAD_NEW = 2;//加载更新public static final String DEFAULT_TAG = "__all__"//所有频道}

(3)文章service接口定义

public interface AppArticleService {/**** @param type 1 加载更多  2 加载更新* @param dto 封装数据* @return 数据列表*/public ResponseResult load(Short type, ArticleHomeDto dto);
}

(4)AppArticleServiceImpl实现类

@Service
public class AppArticleServiceImpl implements AppArticleService {// 单页最大加载的数字private final  static short MAX_PAGE_SIZE = 50;@Autowiredprivate ApArticleMapper apArticleMapper; //默认加载@Autowiredprivate ApUserArticleListMapper apUserArticleListMapper; //用户登录下的加载/**** @param time 时间节点* @param type 1 加载更多  2 加载更新* @param size 每次返回数据量* @return 数据列表*/public ResponseResult load(Short type, ArticleHomeDto dto) {ApUser user = AppThreadLocalUtils.getUser();Integer size = dto.getSize();String tag = dto.getTag();// 分页参数校验if (size == null || size <= 0) {size = 20;}size = Math.min(size,MAX_PAGE_SIZE);dto.setSize(size);//  类型参数校验if (!type.equals(ArticleConstans.LOADTYPE_LOAD_MORE) && !type.equals(ArticleConstans.LOADTYPE_LOAD_NEW))type = ArticleConstans.LOADTYPE_LOAD_MORE;// 文章频道参数验证if (StringUtils.isEmpty(tag)) {dto.setTag(ArticleConstans.DEFAULT_TAG);}// 最大时间处理if(dto.getMaxBehotTime()==null){dto.setMaxBehotTime(new Date());}// 最小时间处理if(dto.getMinBehotTime()==null){dto.setMinBehotTime(new Date());}// 数据加载if(user!=null){return ResponseResult.okResult(getUserArticle(user,dto,type));}else{return ResponseResult.okResult(getDefaultArticle(dto,type));}}/*** 先从用户的推荐表中查找文章,如果没有再从大文章列表中获取* @param user* @param dto* @param type* @return*/private List<ApArticle> getUserArticle(ApUser user,ArticleHomeDto dto,Short type){List<ApUserArticleList> list = apUserArticleListMapper.loadArticleIdListByUser(user,dto,type);if(!list.isEmpty()){List<ApArticle> temp = apArticleMapper.loadArticleListByIdList(list);return temp;}else{return getDefaultArticle(dto,type);}}/*** 从默认的大文章列表中获取文章* @param dto* @param type* @return*/private List<ApArticle> getDefaultArticle(ArticleHomeDto dto,Short type){return apArticleMapper.loadArticleListByLocation(dto,type);}}

(5)ApArticleMapper

public interface ApArticleMapper {/*** 照用户地理位置,加载文章* @param dto   参数封装对象* @param type  加载方向* @return*/List<ApArticle> loadArticleListByLocation(@Param("dto") ArticleHomeDto dto, @Param("type") short type);/*** 依据文章IDS来获取文章详细内容* @param list   文章ID* @return*/List<ApArticle> loadArticleListByIdList(@Param("list") List<ApUserArticleList> list);}

(6)ApArticleMapper 对应xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.model.mappers.app.ApArticleMapper"><resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle"><id column="id" property="id"/><result column="title" property="title"/><result column="author_id" property="authorId"/><result column="author_name" property="authorName"/><result column="channel_id" property="channelId"/><result column="channel_name" property="channelName"/><result column="layout" property="layout"/><result column="flag" property="flag"/><result column="images" property="images"/><result column="labels" property="labels"/><result column="likes" property="likes"/><result column="collection" property="collection"/><result column="comment" property="comment"/><result column="views" property="views"/><result column="province_id" property="provinceId"/><result column="city_id" property="cityId"/><result column="county_id" property="countyId"/><result column="created_time" property="createdTime"/><result column="publish_time" property="publishTime"/><result column="sync_status" property="syncStatus"/></resultMap><sql id="Base_Column_List">id, title, author_id, author_name, channel_id, channel_name, layout, flag, images,labels, likes, collection, comment, views, province_id, city_id, county_id, created_time, publish_time,sync_status</sql><!-- 依据地理位置获取 --><select id="loadArticleListByLocation" resultMap="resultMap">select * from ap_article a<where><if test="dto.provinceId!=null">and a.province_id=#{dto.provinceId}</if><if test="dto.cityId!=null">and a.city_id=#{dto.cityId}</if><if test="dto.countyId!=null">and a.county_id=#{dto.countyId}</if><!-- loadmore --><if test="type != null and type == 1">and a.publish_time <![CDATA[<]]> #{dto.minBehotTime}</if><if test="type != null and type == 2">and a.publish_time <![CDATA[>]]> #{dto.maxBehotTime}</if><if test="dto.tag != '__all__'">and a.channel_id = #{dto.tag}</if></where>limit #{dto.size}</select><!-- 以及文章IDS列表获取文章数据 --><select id="loadArticleListByIdList" resultMap="resultMap">select * from ap_article where id in(<trim prefix="" suffixOverrides=","><foreach item="item" collection="list" separator=",">#{item.articleId},</foreach></trim>)</select>
</mapper>

(7)ApUserArticleListMapper

package com.heima.article.mysql.core.model.mappers.app;import com.heima.article.mysql.core.model.dtos.ArticleHomeDto;
import com.heima.article.mysql.core.model.pojos.app.ApUser;
import com.heima.article.mysql.core.model.pojos.app.ApUserArticleList;
import org.apache.ibatis.annotations.Param;import java.util.List;public interface ApUserArticleListMapper {/*** 按照用户属性阅读习惯,加载文章id* @param user  当前登录的用户* @param dto   参数封装对象* @param type  加载方向* @return*/List<ApUserArticleList> loadArticleIdListByUser(@Param("user") ApUser user, @Param("dto") ArticleHomeDto dto, @Param("type") short type);}

(8)ApUserArticleListMapper 对应xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.article.mysql.core.model.mappers.app.ApUserArticleListMapper">
<resultMap id="BaseResultMap" type="com.heima.model.user.pojos.ApUserArticleList"><id column="id" property="id"/><result column="user_id" property="userId"/><result column="channel_id" property="channelId"/><result column="article_id" property="articleId"/><result column="is_show" property="isShow" javaType="java.lang.Boolean" jdbcType="BIT" /><result column="recommend_time" property="recommendTime" javaType="java.util.Date" jdbcType="TIMESTAMP" /><result column="is_read" property="isRead" javaType="java.lang.Boolean" jdbcType="BIT" /><result column="strategy_id" property="strategyId"/></resultMap><sql id="Base_Column_List">id, user_id, channel_id, article_id, is_show, recommend_time, is_read, strategy_id</sql><select id="loadArticleIdListByUser" parameterType="map" resultMap="BaseResultMap">select<include refid="Base_Column_List" />from ap_user_article_list<where>user_id=#{user.id} and is_show=0 and is_read=0<!-- loadmore --><if test="type != null and type == 1">and recommend_time <![CDATA[<]]> #{dto.minBehotTime}</if><if test="type != null and type == 2">and recommend_time <![CDATA[>]]> #{dto.maxBehotTime}</if><if test="dto.tag != '__all__'">and channel_id = #{dto.tag}</if></where>limit #{dto.size}</select>
</mapper>

(9)单元测试

/*** 测试文章列表相关接口*/
@SpringBootTest(classes = ArticleJarApplication.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class ArticleTest {@Autowiredprivate AppArticleService appArticleService;/*** 测试load*/@Testpublic void testLoad() {ApUser apUser = new ApUser();apUser.setId(1l);AppThreadLocalUtils.setUser(apUser);ArticleHomeDto dto = new ArticleHomeDto();ResponseResult data = appArticleService.load( ArticleConstans.LOADTYPE_LOAD_MORE, dto);System.out.println(data.getData());}}

7.3.4 行为相关功能开发

思路分析

导入heima-leadnews-behavior工程

dto(数据传输对象)的定义:

com.heima.model.behavior.dtos.ShowBehaviorDto

@Data
public class ShowBehaviorDto {// 设备ID@IdEncryptInteger equipmentId;List<ApArticle> articleIds;
}

定义控制器以及控制器接口

(1)接口定义,com.heima.article.apis.BehaviorControllerApi

/*** 行为*/
public interface BehaviorControllerApi {ResponseResult saveShowBehavior(ShowBehaviorDto dto);}

(2)定义控制器:com.heima.behavior.controller.v1.BehaviorController

@RestController
@RequestMapping("/api/v1/behavior")
public class BehaviorController implements BehaviorControllerApi {@Autowiredprivate AppShowBehaviorService appShowBehaviorService;@Override@PostMapping("/show_behavior")public ResponseResult saveShowBehavior(@RequestBody ShowBehaviorDto dto) {return appShowBehaviorService.saveShowBehavior(dto);}}

(3)行为服务层接口:com.heima.behavior.service.AppShowBehaviorService

public interface AppShowBehaviorService {/*** 存储行为数据* @param dto* @return*/public ResponseResult saveShowBehavior(ShowBehaviorDto dto);}

(4)AppShowBehaviorService 实现

@Service
@SuppressWarnings("all")
public class AppShowBehaviorServiceImpl implements AppShowBehaviorService {@Autowiredprivate ApShowBehaviorMapper apShowBehaviorMapper;@Autowiredprivate ApBehaviorEntryMapper apBehaviorEntryMapper;@Overridepublic ResponseResult saveShowBehavior(ShowBehaviorDto dto){ApUser user = AppThreadLocalUtils.getUser();// 用户和设备不能同时为空if(user==null&& (dto.getArticleIds()==null||dto.getArticleIds().isEmpty())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE);}Long userId = null;if(user!=null){userId = user.getId();}ApBehaviorEntry apBehaviorEntry = apBehaviorEntryMapper.selectByUserIdOrEquipment(userId, dto.getEquipmentId());// 行为实体找以及注册了,逻辑上这里是必定有值得,除非参数错误if(apBehaviorEntry==null){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}// 过滤新数据Integer[] temp = new Integer[dto.getArticleIds().size()];for (int i = 0; i < temp.length; i++) {temp[i]=dto.getArticleIds().get(i).getId();}List<ApShowBehavior> list = apShowBehaviorMapper.selectListByEntryIdAndArticleIds(apBehaviorEntry.getId(), temp);List<Integer> stringList = new ArrayList(Arrays.asList(temp));if(!list.isEmpty()){list.forEach(item->{stringList.remove(item.getArticleId());});}// 插入新数据if(!stringList.isEmpty()) {temp = new Integer[stringList.size()];stringList.toArray(temp);apShowBehaviorMapper.saveBehaviors(apBehaviorEntry.getId(), temp);}return ResponseResult.okResult(0);}
}

(5)heima-leadnews-model中定义行为mapper接口:com.heima.article.mysql.core.model.mappers.app.ApShowBehaviorMapper

public interface ApShowBehaviorMapper {/*** 获取以及存在的用户数据* @param entryId* @param articleIds* @return*/List<ApShowBehavior> selectListByEntryIdAndArticleIds(@Param("entryId") Integer entryId, @Param("articleIds") Integer[] articleIds);/*** 保存用户展现行为数据* @param articleIds  文章IDS* @param entryId 实体ID*/void saveBehaviors(@Param("entryId") Integer entryId, @Param("articleIds") Integer[] articleIds);
}

(6)heima-leadnews-model中定义行为mapper文件:mappers/app/ApShowBehaviorMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.heima.article.mysql.core.model.mappers.app.ApShowBehaviorMapper" ><resultMap id="BaseResultMap" type="com.heima.article.mysql.core.model.pojos.app.ApShowBehavior" ><id column="id" property="id" /><result column="entry_id" property="entryId" /><result column="article_id" property="articleId" /><result column="is_click" property="isClick"/><result column="show_time" property="showTime" /><result column="created_time" property="createdTime" /></resultMap><sql id="Base_Column_List" >id, entry_id, article, is_click, show_time, created_time</sql><!-- 选择用户的行为对象,优先按用户选择 --><select id="selectListByEntryIdAndArticleIds" resultMap="BaseResultMap" >select * from ap_show_behavior a where a.entry_id=#{entryId} and article_id in(<foreach item="item" collection="articleIds" separator=",">#{item}</foreach>)</select><insert id="saveBehaviors">/*!mycat:catlet=io.mycat.route.sequence.BatchInsertSequence */insert into ap_show_behavior ( entry_id, article_id,is_click, show_time, created_time) values<foreach item="item" collection="articleIds" separator=",">(#{entryId}, #{item},0, now(),now())</foreach></insert>
</mapper>

(7)ApBehaviorEntryMapper

public interface ApBehaviorEntryMapper {ApBehaviorEntry selectByUserIdOrEquipment(@Param("userId") Integer userId, @Param("equipmentId") Integer equipmentId);}

(8)ApBehaviorEntryMapper 对应映射配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.heima.article.mysql.core.model.mappers.app.ApBehaviorEntryMapper" ><resultMap id="BaseResultMap" type="com.heima.article.mysql.core.model.pojos.app.ApBehaviorEntry" ><id column="id" property="id" /><result column="type" property="type"/><result column="entry_id" property="entryId" /><result column="created_time" property="createdTime" /><result column="burst" property="burst"/></resultMap><sql id="Base_Column_List" >id, type, entry_id, created_time</sql><!-- 选择用户的行为对象,优先按用户选择 --><select id="selectByUserIdOrEquipment" resultMap="BaseResultMap" >select * from ap_behavior_entry a<where><if test="userId!=null">and a.entry_id=#{userId} and type=1</if><if test="userId==null and equipmentId!=null">and a.entry_id=#{equipmentId} and type=0</if></where>limit 1</select>
</mapper>

行为接口测试

/*** 测试文章列表相关接口*/
@SpringBootTest(classes = BehaviorJarApplication.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class BehaviorTest {@Autowiredprivate AppShowBehaviorService showBehaviorService;@Testpublic void testSaveBehavior() {ApUser apUser = new ApUser();apUser.setId(1l);AppThreadLocalUtils.setUser(apUser);ShowBehaviorDto dto = new ShowBehaviorDto();List<ApArticle> articles = new ArrayList<>();ApArticle apArticle = new ApArticle();apArticle.setId(1);articles.add(apArticle);showBehaviorService.saveShowBehavior(dto);//articleIndexService.saveBehaviors(data);}
}

你可能遇到的问题:

问题1:导入工程出现—project出现红线1.修改configure为none.2.修改依赖版本号
问题2:数据库连接不上1.mysql5.7情况下url使用如下:mysql.core.jdbc.url=jdbc:mysql://localhost:3306/hiema_headnews?useUnicode=true&characterEncoding=utf8&useSSL=false

总结功能开发的步骤:
1.在api模块定义好功能接口
2.配置类配置好mysql配置
3.在具体的功能模块下的controller来控制
4.在具体功能模块的service层实现接口和实现类
4.在持久层统一写在了common模块的mapper包和resource下mapper.xml

相关知识点复习:
mybatis
1.trim使用
2.javaType和jdbcType
黑马头条项目 一 项目设计及基础搭建-编程知识网
3.Mybatis的参数传递

  1. MyBatis的传入参数parameterType类型分两种
    基本数据类型:int、string、long、Date; 复杂数据类型:类(JavaBean、Integer等)和Map

  2. 如何获取参数中的值:
    2.1 基本数据类型:#{参数} 获取参数中的值

    2.2 复杂数据类型:#{属性名} ,map中则是#{key}

4.sql标签的使用

最后附上资源:
1.基础工程搭建说明书以及代码
2.数据设计PowerDesigner的使用、设计规范以及sql文件导入资源.