广告管理与缓存解决方案
目标
- 了解网站前台的页面以及广告相关表结构
- 完成运营商后台广告类型管理与广告管理
- 完成前台工程广告轮播图的展示
- 使用SpringDataRedis操作字符串、set、list、hash等类型缓存
- 使用SpringDataRedis实现广告数据的缓存
1. 网站前台分析
1. 网站前台有哪些页面
- 网站首页
- 商家(店铺)页面
- 商品详情页
- 商品搜索页
- 购物车列表页
- 购物选项选择页
- 支付页
- 用户注册页
- 用户登录页
- 用户中心页
等
2. 网站首页广告
- 首页海报(轮播图)
- 今日推荐
- 猜你喜欢
- 楼层广告
3. 数据库表结构分析
tb_content_category 广告分类表
tb_content 广告表
2. 运营商后台-广告类型及广告管理
1. 需求分析
实现广告类型表与广告表的增删改查
2. 准备工作
1. 构建工程
1. content-interface
引入依赖pinyougou-pojo
创建包com.pinyougou.content.service
2. pinyougou-content-service(WAR)
引入依赖同pinyougou-sellergoods-service
引入tomcat插件配置,指定运行端口为9002
为pinyougou-content-service工程提那家web.xml
创建包com.pinyougou.content.service.impl
添加spring相关配置文件 复制sellergoods-service的即可,将它的dobbo的port改为20882,包改为自己的包
3. manager-web工程引入依赖content-interface
2. 生成代码拷贝到工程
拷贝js代码到manager-web
包括service、controller和admin的广告相关代码
3. 测试运行
广告分类管理和广告管理页面
注意:需要将content和sellergoods两个service都开启。
3. 广告管理
1. 广告图片上传
1. 将shop-web的以下资源拷贝到manager-web
- UploadController.java
- uploadService.js
- application.properties
- fdfs_client.conf
2. 在manager-web的springmvc.xml添加配置多媒体解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<!--设置文件上传的最大值5MB-->
<property name="maxUploadSize" value="5242880"/>
</bean>
3. 在contentController.js引入uploadService
app.controller('contentController' ,function($scope,$controller ,contentService,uploadService,contentCategoryService){...}
4. 在content.html引入js
<script type="text/javascript" src="../js/service/uploadService.js"> </script>
5. 在contentController.js编写代码
注意上传出错,写到error
// 上传图片
$scope.uploadFile = function () {
uploadService.uploadFile().success(
function (response) {
if(response.success){
// 上传成功,取出url
$scope.entity.pic = response.message;// 设置文件地址
}else{
alert(response.message);
}
}
).error(
function () {
alert("上传出错!");
}
);
}
6. 修改content.html实现上传功能
<td>图片</td>
<td>
<input type="file" id="file">
<button ng-click="uploadFile()">上传图片</button>
<img alt="" src="{{entity.pic}}" height="100px" width="200px">
</td>
2. 广告类目选择
将contentCategoryService引入contentController,见之前代码
在content.html引入contentCategoryService.js
在contentController.js添加代码
// 加载广告分类的列表
$scope.findContentCategoryList = function () {
contentCategoryService.findAll().success(
function (response) {
$scope.contentCategoryList = response;
}
)
}
在content.html初始化该调用方法
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="contentController" ng-init="findContentCategoryList()" >
将广告分类改为下拉列表
<td>内容类目ID</td>
<td>
<select class="form-control" ng-model="entity.categoryId"
ng-options="item.id as item.name for item in contentCategoryList"></select>
</td>
3. 广告状态
修改content.html
<td>状态</td>
<td>
<input type="checkbox" ng-model="entity.status"
ng-true-value="1" ng-false-value="0" placeholder="状态" >
</td>
修改contentController.js
$scope.status = ["无效","有效"];
修改content.html的列表
<td>
<img src="{{entity.pic}}" alt="" height="50px" width="100px">
</td>
<td>{{status[entity.status]}}</td>
一个是状态的修改,一个是图片的展示
3. 网站首页- 广告展示
1. 需求分析
修改首页,当其轮播广告图根据后台设置的广告列表动态生成
2. 准备工作
1. 工程搭建
各种配置文件,不需要springsecurity框架
另外,pom.xml配置的tomcat穹顶端口为9103
2. 前端
拷贝资源,将静态资源里面的index.html和相关目录都拷过去
添加angularJS库
在js文件夹拷贝base.js和base_pagination.js,创建service和controller文件夹
3. 后端代码
1. 服务接口层
在content-interface工程ContentService接口增加方法
// 根据广告类型id查询列表
public List<TbContent> findByCategoryId(Long categoryId);
2. 服务实现层
content-service的ContentServiceImpl类增加方法
@Override
public List<TbContent> findByCategoryId(Long categoryId) {
TbContentExample example = new TbContentExample();
Criteria criteria = example.createCriteria();
criteria.andCategoryIdEqualTo(categoryId);
criteria.andStatusEqualTo("1");//状态必须为1
example.setOrderByClause("sort_order");// 排序
return contentMapper.selectByExample(example);
}
3. 控制层
在portal-web创建控制器类ContentController
@RestController
@RequestMapping("/content")
public class ContentController {
@Reference
private ContentService contentService;
@RequestMapping("/findByCategoryId")
public List<TbContent> findByCategoryId(Long categoryId) {
return contentService.findByCategoryId(categoryId);
}
}
4. 前端代码
1. 服务层
在portal-web工程创建contentService.js
app.service("contentService",function ($http) {
// 根据分类id查询广告列表
this.findByCategoryId=function (categoryId) {
return $http.get("content/findByCategoryId.do?categoryId="+categoryId);
}
});
2. 控制层
在portal-web创建contentController.js
app.controller("contentController",function ($scope, contentService) {
$scope.contentList = [];//广告集合
// contentList[1] 是轮播图,contentList[2] 是今日推荐
$scope.findByCategoryId = function (categoryId) {
contentService.findByCategoryId(categoryId).success(
function (response) {
$scope.contentList[categoryId] = response;
}
);
}
});
3. 页面
修改portal-web的index.html引入js
<script src="plugins/angularjs/angular.min.js"></script>
<script type="text/javascript" src="js/base.js"></script>
<script type="text/javascript" src="js/service/contentService.js"></script>
<script type="text/javascript" src="js/controller/contentController.js"></script>
在body上添加指令
<body ng-app="pinyougou" ng-controller="contentController" ng-init="findByCategoryId(1)">
修改轮播图
<!--banner轮播-->
<div id="myCarousel" data-ride="carousel" data-interval="4000" class="sui-carousel slide">
<ol class="carousel-indicators">
<li data-target="#myCarousel" data-slide-to="{{$index}}" class="{{$index==0?'active':''}}"
ng-repeat="item in contentList[1]"></li>
</ol>
<div class="carousel-inner">
<div class="{{$index==0?'active':''}} item" ng-repeat="item in contentList[1]">
<a href="{{item.url}}">
<img src="{{item.pic}}" />
</a>
</div>
启动后再地址栏输入页面,看到首页效果
4. SpringDataRedis
1. 项目常见问题思考
项目实现了广告后台管理和广告前台展示,但是对于首页来说,每天大量用户访问,对数据库压力巨大,如何解决?一种是数据缓存,一种是网页静态化。今天看第一种方案
2. redis和Jedis
参见自己的java-web3的笔记
3. Spring Data Redis
是spring家族的一员,提供在spring应用中通过简单的配置访问redis服务,对redis底层开发包(Jedis,JRedis,RJC)进行高度封装,RedisTemplate提供redis的各种操作,异常处理以及序列化,支持发布订阅,对spring 3.1 cache进行实现。
针对jedis提供的功能:
- 连接池自动管理,提供了一个高度封装的“RedisTemplate”类
- 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
- ValueOperations:简单K-V操作
- SetOperations:set类型数据操作
- ZSetOperations:zset类型数据操作
- HashOperations:针对map类型的数据操作
- ListOperations:针对list类型的数据操作
4. 入门demo
1. 准备工作
构架maven项目,引入spring和junit的依赖
引入Jedis和SpringDataRedis依赖。
<!--缓存-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>
在src/main/resources下创建properties文件将爱,建立redis-config.properties
# Redis settings
# server IP
redis.host=127.0.0.1
# server port
redis.port=6379
# server pass
redis.pass=
# use dbIndex
redis.database=0
# \u63A7\u5236\u4E00\u4E2Apool\u6700\u591A\u6709\u591A\u5C11\u4E2A\u72B6\u6001\u4E3Aidle(\u7A7A\u95F2\u7684)\u7684jedis\u5B9E\u4F8B
redis.maxIdle=300
# \u8868\u793A\u5F53borrow(\u5F15\u5165)\u4E00\u4E2Ajedis\u5B9E\u4F8B\u65F6\uFF0C\u6700\u5927\u7684\u7B49\u5F85\u65F6\u95F4\uFF0C\u5982\u679C\u8D85\u8FC7\u7B49\u5F85\u65F6\u95F4(\u6BEB\u79D2)\uFF0C\u5219\u76F4\u63A5\u629B\u51FAJedisConnectionException\uFF1B
redis.maxWait=3000
# \u5728borrow\u4E00\u4E2Ajedis\u5B9E\u4F8B\u65F6\uFF0C\u662F\u5426\u63D0\u524D\u8FDB\u884Cvalidate\u64CD\u4F5C\uFF1B\u5982\u679C\u4E3Atrue\uFF0C\u5219\u5F97\u5230\u7684jedis\u5B9E\u4F8B\u5747\u662F\u53EF\u7528\u7684
redis.testOnBorrow=true
创建spring文件夹,创建applicationContext-redis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<context:property-placeholder location="classpath*:properties/*.properties" />
<!-- redis 相关配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
</beans>
其中maxIdle:最大空闲数;maxWaitMills:连接时的最大等待毫秒数;testOnBorrow:在提取一个jedis实例时,是否提前进行验证操作;如果true,得到的jedis实例是可用的。
2. 值类型操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext-redis.xml")
public class ValueTest {
@Autowired
private RedisTemplate template;
@Test
public void setValue(){
template.boundValueOps("namea").set("itcast");
}
@Test
public void getValue(){
String str = (String) template.boundValueOps("name").get();
System.out.println(str);
}
@Test
public void deleteValue(){
template.delete("name");
}
}
3. set类型操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext-redis.xml")
public class SetTest {
@Autowired
private RedisTemplate template;
@Test
public void setValue(){
template.boundSetOps("nameSet").add("曹操");
template.boundSetOps("nameSet").add("刘备");
template.boundSetOps("nameSet").add("孙权");
}
@Test
public void getValue(){
Set set = template.boundSetOps("nameSet").members();
System.out.println(set);
}
@Test
public void removeValue(){
template.boundSetOps("nameSet").remove("孙权");
}
@Test
public void delete(){
template.delete("nameSet");
}
}
4. List类型操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext-redis.xml")
public class ListTest {
@Autowired
private RedisTemplate template;
// 右压栈:后添加的元素排在后边
@Test
public void testSetValue1(){
template.boundListOps("nameList1").rightPush("刘备");
template.boundListOps("nameList1").rightPush("关羽");
template.boundListOps("nameList1").rightPush("张飞");
}
@Test
public void testGetValue1(){
List list = template.boundListOps("nameList1").range(0, 10);
System.out.println(list);
}
// 左压栈
@Test
public void testSetValue2(){
template.boundListOps("nameList2").leftPush("刘备");
template.boundListOps("nameList2").leftPush("关羽");
template.boundListOps("nameList2").leftPush("张飞");
}
@Test
public void testGetValue2(){
List list = template.boundListOps("nameList2").range(0, 10);
System.out.println(list);
}
// 显示左压栈的值,根据索引查询元素
@Test
public void testSearchByIndex(){
String str = (String) template.boundListOps("nameList1").index(1);
System.out.println(str);
}
// 移除某个元素的值 参数1为移除的个数
@Test
public void testRemoveValue(){
template.boundListOps("nameList1").remove(1,"刘备");
}
}
5. hash类型操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext-redis.xml")
public class HashTest {
@Autowired
private RedisTemplate template;
@Test
public void testSetValue(){
template.boundHashOps("nameHash").put("a","tom");
template.boundHashOps("nameHash").put("b","jack");
template.boundHashOps("nameHash").put("c","rose");
template.boundHashOps("nameHash").put("d","pony");
}
// 获取所有的key
@Test
public void testGetKeys(){
Set keys = template.boundHashOps("nameHash").keys();
System.out.println(keys);
}
// 获取所有的值
@Test
public void testGetValues(){
List list = template.boundHashOps("nameHash").values();
System.out.println(list);
}
// 根据key取值
@Test
public void searchValueByKey(){
String str = (String) template.boundHashOps("nameHash").get("b");
}
// 移除某个小key的值
@Test
public void removeValue(){
template.boundHashOps("nameHash").delete("c");
}
}
5. 网站首页-缓存广告数据
1. 需求分析
现在首页的广告每次都是从数据库读取,这样网站数据库压力很大,并且影响执行效率。需要将这部分数据缓存。
2. 读取缓存
1. 公共组件层
缓存对于整个的系统是通用功能。其他部分的数据可能也需要,放置到common里面。
1. 引入依赖
<!--缓存-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>
2. 创建配置文件
将redis-config.properties和applicationContext-redis.xml拷贝到common对应的部分
3. content-service 依赖common
2. 后端服务实现层
修改content-service的ContentServiceImpl
@Override
public List<TbContent> findByCategoryId(Long categoryId) {
List<TbContent> list = (List<TbContent>) redisTemplate.boundHashOps("content").get(categoryId);
if(list==null){
System.out.println("get data from db");
TbContentExample example = new TbContentExample();
Criteria criteria = example.createCriteria();
criteria.andCategoryIdEqualTo(categoryId);
criteria.andStatusEqualTo("1");//状态必须为1
example.setOrderByClause("sort_order");// 排序
list= contentMapper.selectByExample(example);
redisTemplate.boundHashOps("content").put(categoryId,list);
}else{
System.out.println("get data from redis");
}
return list;
}
3. 更新缓存
广告数据发生变更后,需要将缓存数据清除,再次查询才能获取最新数据
1. 新增广告后清除缓存
修改content-service的ContentServiceImpl.java
@Override
public void add(TbContent content) {
contentMapper.insert(content);
// 清除缓存
redisTemplate.boundHashOps("content").delete(content.getCategoryId());
}
// 修改的话可能修改广告的分类,需要把原分类的缓存和新分类的缓存都删除
@Override
public void update(TbContent content){
// 查询修改前的分类id
Long oldCategoryId = contentMapper.selectByPrimaryKey(content.getId()).getCategoryId();
redisTemplate.boundHashOps("content").delete(oldCategoryId);
contentMapper.updateByPrimaryKey(content);
// 如果分类id发生修改,清除修改后的分类id的缓存
if(oldCategoryId.longValue()!=content.getCategoryId().longValue()){
redisTemplate.boundHashOps("content").delete(content.getCategoryId());
}
}
@Override
public void delete(Long[] ids) {
for(Long id:ids){
// 先清除缓存
// 广告分类的id
Long categoryId = contentMapper.selectByPrimaryKey(id).getCategoryId();
redisTemplate.boundHashOps("content").delete(categoryId);
contentMapper.deleteByPrimaryKey(id);
}
}