电商项目笔记

电商项目笔记

【第一章】、环境搭建

【第二章】

1、AngularJS框架的使用

(1)AngularJS四大特性

[ 1 ]mvc模式

​ Angular 遵循软件工程的 MVC 模式,并鼓励展现,数据,和逻辑组件之间的松耦合.通过依赖注入(dependency injection),Angular 为客户端的 Web 应用带来了传统服务端的服务,例如独立于视图的控制。 因此,后端减少了许多负担,产生了更轻的 Web 应用。

[ 2 ]双向绑定

​ 【课件解析】:AngularJS是建立在这样的信念上的:声明式编程应该用于构建用户界面以及编写软件构建,而指令式编程非常适合来表示业务逻辑。框架采用并扩展了传统的HTML,通过双向的数据绑定来适应动态内容,双向的数据绑定允许模型和视图之间的自动同步。因此,AngularJS使得对DOM的操作不再重要并提升了可测试性。

​ 初步个人理解:

​ [1]传统的HTML通过DOM对象来获取和改变文本框的内容,而Angular通过特定的表达式来获取。

​ [2]AngularJS具有双向绑定特性,当两个或多个文本框的值绑定时,改变其中一个,其它的也随之改变。

[ 3 ]依赖注入

​ 【课件解析】:依赖注入(Dependency Injection,简称 DI)是一种设计模式,指某个对象依赖的其他对象无需手工创建,只需要”吼一嗓子“,因此对象在创建时,其依赖的对象由框架来自动创建并注入进来,其实就是最少知识法则;模块中所有的service和provider两类对象,都可以根据形参名称实现DI。

[ 4 ]模块化设计

​ 【课件解析】:高内聚低耦合法则

​ [1] 官方提供的模块 ng、ngRoute、ngAnimate

​ [2] 用户自定义的模块 angular.module{‘模块名’,[ ]}

(2)、常用命令

[ 1 ]ng-app

​ ng-app 指令作用是告诉子元素以下的指令是归angularJS的,AngularJS会识别的

​ 该指令定义了AngularJS应用程序的根元素

[ 2 ]ng-model

​ ng-model 指令用于绑定变量,这样用户在文本框输入的内容会绑定到变量上,而表达式可以实时输出变量

[ 3 ]ng-init

​ ng-init 指令用来对变量初始化赋值

[ 4 ]ng-controller

​ ng-controller 用于指定使用的控制器

[ 5 ]$scope

s c o p e 的 使 用 贯 穿 整 个 A n g u l a r J S A p p 应 用 , 它 与 数 据 模 型 相 关 联 , 同 时 也 是 表 达 式 执 行 的 上 下 文 . 有 了 scope 的使用贯穿整个 AngularJS App 应用,它与数据模型相关联,同时也是表达式执行的上 下文.有了 scope使穿AngularJSApp,,.scope 就在视图和控制器之间建立了一个通道,基于作用域视图在修改数据时会
立刻更新 $scope,同样的 $scope 发生改变时也会立刻重新渲染视图.

[ 6 ]ng-repeat

​ ng-repeat 用来循环,类似于增强for

(3)、入门小demo

【demo1】{{表达式}}

​ 作用:获取对象值

​ 案例所使用命令ng-app

<html>
<head>
	<title>入门小 Demo-1</title>
	<script src="angular.min.js"></script></head>
<body ng-app>
	{{100+100}}
</body>
</html>

执行结果为:200

【demo2】双向绑定

​ 案例所使用命令ng-model

<html>
<head>
	<title>入门小 Demo-1 双向绑定</title>		
	<script src="angular.min.js"></script></head>
<body ng-app>
	请输入你的姓名:<input ng-model="myname"><br>
	{{myname}},你好
</body>
</html>

运行结果如下:

​ 请输入你的姓名:张三丰

​ 张三丰,你好

【demo3】初始化指令

​ 案例所用命令ng-init

<html>
  <head>
    <title>入门小 Demo-3 初始化</title>
    <script src="angular.min.js"></script>
  </head>
  <body ng-app ng-init="myname='张根硕'">
    请输入你的姓名:<input ng-model="myname"><br>
    {{myname}},你好
  </body>
</html>

【demo4】控制器

​ 案例所用命令:ng-controller $scope

<html>
<head>
	<title>入门小 Demo-3 初始化</title>
	<script src="angular.min.js"></script>
	<script>
		var app=angular.module('myApp',[]); //定义了一个叫 myApp 的模块
		//定义控制器
		app.controller('myController',function($scope){
			$scope.add=function(){
				return parseInt($scope.x)+parseInt($scope.y);
			}
        });
	</script>
</head>
<body ng-app="myApp" ng-controller="myController">
	x:<input ng-model="x" > y:<input ng-model="y" >
	运算结果:{{add()}}
</body>
</html>

运行结果如下:

​ X:333 Y:444 运行结果:777

【demo5】事件指令

<html>
<head>
	<title>入门小 Demo-5 事件指令</title>
    <script src="angular.min.js"></script>
    <script>
        //定义了一个叫 myApp 的模块
        var app=angular.module('myApp',[]); 
		//定义控制器
		app.controller('myController',function($scope){
			$scope.add=function(){
				$scope.z=parseInt($scope.x)+parseInt($scope.y);
            }
        });
    </script>
</head>
<body ng-app="myApp" ng-controller="myController">
	x:<input ng-model="x" >
	y:<input ng-model="y" >
	<button ng-click="add()">运算</button>结果:{{z}}
</body>
</html>

运算结果:点击运算,显示结果

ng-click 是最常用的单击事件指令,再点击时触发控制器的某个方法

【demo6】循环数组

<html>
<header>
	<title>入门小demo6 循环数组</title>
	<script src="angular.min.js"></script>
	<script>
	//创建模块
	var app = angular.module("myApp",[]);
	//创建控制器
	app.controller("myController",function($scope){
		//创建集合
		$scope.list=[111,222,333,444,555];
	})
	</script>
</header>
<body ng-app="myApp" ng-controller="myController">
<table>
	<tr ng-repeat="x in list">
		<td>{{x}}</td>
	</tr>
</table>
</body>
</html>

运行结果:

​ 111

​ 222

​ 333

​ 444

​ 555

【demo7】循环对象数组

<html>
  
<header>
	<title>入门小demo7 循环对象数组</title>
	<script src="angular.min.js"></script>
	<script>
	//创建模块
	var app = angular.module("myApp",[]);
	//创建控制器
	app.controller("myController",function($scope){
		//创建集合
		$scope.list=[
			{name:'张三',math:100,english:100},
			{name:'李四',math:95,english:92},
			{name:'老王',math:0,english:0}
		];
	})
	</script>
</header>
<body ng-app="myApp" ng-controller="myController">
<table>
	<tr>
		<td>姓名</td>
		<td>数学</td>
		<td>英语</td>
	</tr>
	<tr ng-repeat="student in list">
		<td>{{student.name}}</td>
		<td>{{student.math}}</td>
		<td>{{student.english}}</td>
	</tr>
</table>
</body>
  
</html>

运行结果如下:

姓名数学英语
张三100100
李四9592
老王00

【demo8】内置服务

html页面

<html>
<head>
	<title>入门小 Demo-8 内置服务</title>
	<meta charset="utf-8" />
	<script src="angular.min.js"></script>
	<script>
		var app=angular.module('myApp',[]); //定义了一个叫 myApp 的模块
		//定义控制器
		app.controller('myController',function($scope,$http){
			$scope.findAll=function(){
				$http.get('data.json').success(
					function(response){
						$scope.list=response;
					}
				);
			}
		});
	</script>
</head>
<body ng-app="myApp" ng-controller="myController" ng-init="findAll()">
	<table>
		<tr>
			<td>姓名</td>
  			<td>数学</td>
  			<td>语文</td>
		</tr>
		<tr ng-repeat="entity in list">
			<td>{{entity.name}}</td>
			<td>{{entity.shuxue}}</td>
			<td>{{entity.yuwen}}</td>
		</tr>
	</table>
</body>
  
</html>

建立文件 data.json

[
  {"name":"张三","shuxue":100,"yuwen":93},
  {"name":"李四","shuxue":88,"yuwen":87},
  {"name":"老王","shuxue":77,"yuwen":56}
]

运行结果:

姓名数学英语
张三10093
李四8887
老王7756

【第四章】商家

1、商家入驻

【代码逻辑】:商家注册商家信息,将数据存储到数据库中

【实现步骤】:(1)注册表单的name属性需要与实体类字段相对应

​ (2)在Web层Controller类中加入状态和创建时间

@RequestMapping("/add")
	public Result add(@RequestBody TbSeller seller){
		try {
          	//设置状态
			seller.setStatus("0");
          	//设置创建时间
			seller.setCreateTime(new Date());
			sellerService.add(seller);
			return new Result(true, "增加成功");
		} catch (Exception e) {
			e.printStackTrace();
			return new Result(false, "增加失败");
		}
	}

2、商家审核

【代码逻辑】:在商家管理页面中显示待审核的商家,创建updateStatus(String sellerId,String status)方法来进行修改商家审核状态

【实现步骤】:

(1)在审核按钮分别绑定点击事件调用updateStatus,传入商家ID和要修改的状态值

		<div class="modal-footer">						
			<button class="btn btn-success" data-dismiss="modal" aria-hidden="true"  ng-click="updateStatus(entity.sellerId,'1')">审核通过</button>
         	<button class="btn btn-danger"  data-dismiss="modal" aria-hidden="true"  ng-click="updateStatus(entity.sellerId,'2')">审核未通过</button>
            <button class="btn btn-danger" data-dismiss="modal" aria-hidden="true"   ng-click="updateStatus(entity.sellerId,'3')">关闭商家</button>
			<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">关闭</button>
		</div>

(2)前端controller代码,

$scope.updateStatus=function(sellerId,status){
		//调用service
		sellerService.updateStatus(sellerId,status).success(
			function(response){
				//返回值为Result类型
				if(response.success){
					//操作成功,重新加载
					$scope.reloadList();
				}else{
					//操作失败,弹出提示
					alert(response.message);
				}
			}
		)
	}

(3)后端Service代码,先通过id查询商家信息,然后设置状态值再调用存储方法

@Override
	public void updateStatus(String sellerId, String status) {
		// 根据id查询商家
		TbSeller seller = sellerMapper.selectByPrimaryKey(sellerId);
		// 修改商家审核状态
		seller.setStatus(status);
		// 修改商家信息
		sellerMapper.updateByPrimaryKey(seller);
	}

###3、商家系统登录【SpringSecurity】

【代码逻辑】:使用SpringSecurity进行登录验证,前端登录传参调用SpringSecurity的验证方法,来实现校验用 户账号密码以及权限;

【实现步骤】:

(1)导入springsecurity的jar包依赖

<!-- 身份验证  -->
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-config</artifactId>
	</dependency>

(2)配置web-xml

 <!-- 配置认证 -->
	 <!-- 这个用来加载spring-security的配置文件 -->
	 <context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring/spring-security.xml</param-value>
	 </context-param>
	<!-- 配置监听器在服务器启动时加载配置文件 -->
	 <listener>
		<listener-class>
			org.springframework.web.context.ContextLoaderListener
		</listener-class>
	 </listener>
	<!-- 配置监听器,固定写法,底层通过springSecurityFilterChain创建实例对象-->
	 <filter>  
		<filter-name>springSecurityFilterChain</filter-name>  
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
	 </filter>  
	 <filter-mapping>  
		<filter-name>springSecurityFilterChain</filter-name>  
		<url-pattern>/*</url-pattern>  
	 </filter-mapping>

(3)配置springsecurity.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans 
	xmlns="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans" 
	xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
						http://www.springframework.org/schema/beans/spring-beans.xsd
						http://code.alibabatech.com/schema/dubbo 
    					http://code.alibabatech.com/schema/dubbo/dubbo.xsd
						http://www.springframework.org/schema/security 
						http://www.springframework.org/schema/security/spring-security.xsd">
	
  	<!-- 这里配置认证需要过滤的资源 -->
  	<http pattern="/*.html"  security="none"/>
	<http pattern="/css/**"  security="none"/>
	<http pattern="/img/**"  security="none"/>
	<http pattern="/js/**"  security="none"/>
	<http pattern="/plugins/**"  security="none"/>
    <!-- 注意要过滤掉注册方法 -->
	<http pattern="/seller/add.do" security="none"/> 
	
  
  	<!-- 页面拦截规则 -->
  	<!-- 配置SPEL表达式是否开启 若开启access需要使用HasRole('ROLE_XXX')  -->
	<http use-expressions="false">
		<intercept-url pattern="/**"  access="ROLE_SELLER"/>
		<form-login login-page="/shoplogin.html"  //登录页面
					default-target-url="/admin/index.html"  //默认访问路径
					authentication-failure-url="/shoplogin.html"  //登录失败跳转路径
					always-use-default-target="true"     //登录成功后访问默认路径
					 />
      	<!-- 跨域请求,由于html无法通过验证,需要开启此配置通过验证-->
		<csrf disabled="true"></csrf>
      	<!-- 过滤内置框架页面 -->
		<headers>
			<frame-options policy="SAMEORIGIN" />
		</headers>
		<logout/>
	</http>
	
	<!-- 认证管理器 -->
	<authentication-manager>
		<authentication-provider user-service-ref="userService" ></authentication-provider>
	</authentication-manager>
	
	<!-- 注入自定义认证类 -->
	<beans:bean id="userService" class="com.pinyougou.service.UserDetailsServiceImpl">
		<beans:property name="sellerService" ref="sellerService"/>
	</beans:bean>
	
	<!-- 引用dubbo服务 -->
	<dubbo:application name="pingyougou-web-shop"/>
	<dubbo:registry address="zookeeper://192.168.25.128:2181"/>
	<dubbo:reference                 		           id="sellerService"interface="com.pinyougou.sellergoods.service.SellerService">
    </dubbo:reference>
	
		
</beans:beans>

(4)编写自定义验证类UserDetailsServiceImpl

package com.pinyougou.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.pinyougou.pojo.TbSeller;
import com.pinyougou.sellergoods.service.SellerService;
//实现UserDetailsService接口
public class UserDetailsServiceImpl implements UserDetailsService {
	//定义服务类但在xml通过配置注入
	private SellerService sellerService;
	//xml注入需要set方法
	public void setSellerService(SellerService sellerService) {
		this.sellerService = sellerService;
	}

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		//创建角色集合,用来存储用户角色信息
		List<GrantedAuthority> authorities = new ArrayList<>();
		//根据用户名查询商家
		TbSeller seller = sellerService.findOne(username);
		//判断用户名是否存在
		if(seller!=null) {
			//查询到商家
			//判断商家状态是否通过审核
			if("1".equals(seller.getStatus())) {
				//商家已通过审核
				//给商家添加角色
				authorities.add(new SimpleGrantedAuthority("ROLE_SELLER"));
				return new User(username, seller.getPassword(), authorities);
			}else {
				//商家未审核通过,不允许登录
				return null;
			}
		}else {
			//未查询到商家,不允许登录
			return null;
		}
	}
}

(5)页面表单修改

<!-- 配置访问路径与springsecurity一致,默认为/login  方法必须为post请求 -->
<form class="sui-form" id="loginForm" action="/login" method="post">
	<div class="input-prepend"><span class="add-on loginname"></span>
       <!-- 定义账号与springsecurity配置一致 默认username -->
		<input id="prependedInput" type="text" name="username" placeholder="邮箱/用户名/手机号" class="span2 input-xfat">
	</div>
	<div class="input-prepend"><span class="add-on loginpwd"></span>
      <!-- 定义密码与springsecurity配置一致 默认password -->
		<input id="prependedInput" type="password" name="password" placeholder="请输入密码" class="span2 input-xfat">
	</div>
	<div class="setting">
		<label class="checkbox inline">
			<input name="m1" type="checkbox" value="2" checked="">自动登录
		</label>
		<span class="forget">忘记密码?</span>
	</div>
	<div class="logined">
      <!-- 绑定点击事件,提交表单 -->
		<a class="sui-btn btn-block btn-xlarge btn-danger" onclick="document:loginForm.submit()" target="_blank">&nbsp;&nbsp;</a>
	</div>
</form>

4、商家密码加密【Bcrypt】

【Bcrypt】个人概述:是一款加密算法,会自动生成一个随机盐来实现加密,同时把盐的值随机混入加密后的密码中,加密解密都由它来完成。生成长度为60的密文。由于是随机盐,相同密码两次加密后的值基本会不一样。

【代码逻辑】:在存储密码的时候进行Bcrypt加密,并在配置文件中配置加密方式

【实现步骤】:

(1)在注册add方法中对密码进行加密

	@RequestMapping("/add")
	public Result add(@RequestBody TbSeller seller){
		try {
		//创建加密类
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
			//【对密码进行加密】
			String password = passwordEncoder.encode(seller.getPassword());
			//存储加密后的密码
			seller.setPassword(password);
			//设置初始状态值
			seller.setStatus("0");
			//设置添加日期
			seller.setCreateTime(new Date());
			sellerService.add(seller);
			return new Result(true, "增加成功");
		} catch (Exception e) {
			e.printStackTrace();
			return new Result(false, "增加失败");
		}
	}

(2)在spring-security.xml中配置加密方式

<!-- 认证管理器 -->
	<authentication-manager>
		<authentication-provider user-service-ref="userService" >
          	<!-- 配置加密方式  -->
			<password-encoder ref="passwordEncoder"></password-encoder>
		</authentication-provider>
	</authentication-manager>
	
	<!-- 配置加密类 -->
	<beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

【第五章】商品

1、商品分类列表

​ 【代码逻辑】:定义findByParentId(String parentId)方法,与点击【查询下级】按钮绑定,并参数设置为查询商品的id,用此id作为下一级的parentId的查询条件来查询。在页面头部定义ng-init(0)用来初始化查询第一级商品分类。

​ 【实现步骤】

​ (1)Web层Service代码:定义方法,调用dao通过parentId查询下一级商品分类信息

		@Override
		public List<TbItemCat> findByParentId(Long parentId) {
			//创建条件对象
			TbItemCatExample example = new TbItemCatExample();
			Criteria createCriteria = example.createCriteria();
			//添加parentId查询条件
			createCriteria.andParentIdEqualTo(parentId);
			//查询数据库
			List<TbItemCat> itemCatList = itemCatMapper.selectByExample(example);
			//返回查询结果
			return itemCatList;
		}

​ (2)Web层Controller代码:定义方法,调用service层

@RequestMapping("/findByParentId")
	public List<TbItemCat> findByParentId(Long parentId){
		return itemCatService.findByParentId(parentId);
	}

​ (3)前端service代码、controller代码:总结在面包屑中

2、面包屑

​ 【代码逻辑】AngularJS中定义3个变量

​ $scope.grade //分类等级

​ $scope.entity_1 //查询二级分类所需要的对象(该对象所属一级分类)

​ $scope.entity_2 //查询三级分类所需要的对象(该对象所属二级分类)

​ 定义selectList(p_entity)方法,参数为需要查询下一级分类的对象

​ 在selectList方法方法里需要先判定分类等级,然后通过对应等级来给面包屑对应的对象赋值。此外还需要在调用查询方法前先对分类等级的值进行+1处理。

​ 【实现步骤】

​ (1)前端Controller

	//查询下一级商品分类
	$scope.findByParentId=function(parentId){
		itemCatService.findByParentId(parentId).success(
			function(response){
				$scope.list=response;
			}
		)
	}
    
	//定义初始分类等级
	$scope.grade=1;
	//设置分类等级
	$scope.setGrade=function(value){
		$scope.grade=value;
	}

	//设置面包屑显示
	$scope.selectList=function(p_entity){
		//如果为一级分类,直接查询
		if($scope.grade==1){
			$scope.entity_1=null;
			$scope.entity_2=null;
		}
		//如果为二级分类,给二级赋值再查询
		if($scope.grade==2){
			$scope.entity_1=p_entity;
			$scope.entity_2=null;
		}
		//如果为二级分类,给三级赋值再查询
		if($scope.grade==3){
			$scope.entity_2=p_entity;
		}
		//调用查询商品分类
		$scope.findByParentId(p_entity.id);
	}

​ (2)html

​ 面包屑展示 部分


  <ol class="breadcrumb">	                        	
      <li>
          <a href="#" ng-click="setGrade(1);selectList({id:0})">顶级分类列表</a> 
      </li>
      <li>
      <a href="#" ng-click="setGrade(2);selectList(entity_1)">{{entity_1.name}}</a>
      </li>
      <li>
       <a href="#" ng-click="setGrade(3);selectList(entity_2)">{{entity_2.name}}</a>
      </li>
  </ol>

​ 数据展示部分:

<tr ng-repeat="entity in list">
  <td><input  type="checkbox" ></td>			                              
  <td>{{entity.id}}</td>
  <td>{{entity.name}}</td>									    
  <td>
    {{entity.typeId}}    
  </td>									      
  <td class="text-center">	
    <!-- 但分类等级为3时,不展示查询下级 -->
    <span ng-if="grade<3">                                     
      <button type="button" class="btn bg-olive btn-xs" ng-click="setGrade(grade+1);selectList(entity)">查询下级</button> 		                           
    </span>	
    <button type="button" class="btn bg-olive btn-xs" data-toggle="modal" data-target="#editModal" >修改</button>                                           
  </td>
</tr>

​ 需要注意的是[ 1 ]:在查询方法前需要先对分类等级进行赋值

​ [ 2 ]:通过【ng-if】判定分类等级来实现当等级为3时不显示查询下级

3、商品录入-商品基本信息录入

【代码逻辑】

【实现步骤】

【后端】

(1)DaoMapper的修改:

在商品表的insert方法中加入selectKey,获取存入后生成的id值

<selectKey resultType="java.lang.Long" order="after" keyProperty="id">
	SELECT	LAST_INSERT_ID() AS id
</selectKey>

(2)创建组合类对象

public class Goods implements Serializable{
	private TbGoods tbGoods;	//商品SPU基本信息
	private TbGoodsDesc tbGoodsDesc;	//商品SPU扩展信息
	private List<TbItem> itemsList;		//商品SKU信息
  
  	//getter,setter方法...
}

(3)修改goodsService中add()方法

@Override
	public void add(Goods goods) {
		TbGoods tbGoods = goods.getTbGoods();//获取商品信息对象
		TbGoodsDesc tbGoodsDesc = goods.getTbGoodsDesc();//获取商品扩展信息对象
		//状态:未审核
		tbGoods.setAuditStatus("0");
		//存储商品基本信息
		goodsMapper.insert(tbGoods);
		//在商品扩展信息中存入商品id
		tbGoodsDesc.setGoodsId(tbGoods.getId());
		//存入商品扩展信息
		goodsDescMapper.insert(tbGoodsDesc);
	}

(4)控制层绑定商家id

@RequestMapping("/add")
	public Result add(@RequestBody Goods goods){
		try {
			//获取当前登录商家的id
			String sellerId = SecurityContextHolder.getContext().getAuthentication().getName();
			//绑定商家id
			goods.getTbGoods().setSellerId(sellerId);
			//调用service
			goodsService.add(goods);
			return new Result(true, "增加成功");
		} catch (Exception e) {
			e.printStackTrace();
			return new Result(false, "增加失败");
		}
	}

(5)前端controller

	//保存 
	$scope.add=function(){	
		goodsService.add($scope.entity).success(
			function(response){
				if(response.success){
					alert("添加成功");
					$scope.entity={};
				}else{
					alert(response.message);
				}
			}		
		);				
	}

(6)前端html:绑定输入框ng-momel ,和保存的点击按钮的ng-click

4、富文本编辑器

(1)html表单代码

<div class="col-md-2 title editer">商品介绍</div>
<div class="col-md-10 data editer">
  <textarea name="content"  style="width:800px;height:400px;visibility:hidden;" ></textarea>
</div>

(2)修改前端controller代码

//保存 
	$scope.add=function(){	
      
		//添加富文本框内容【修改内容】
		$scope.entity.tbGoodsDesc.introduction=editor.html();
      
		goodsService.add($scope.entity).success(
			function(response){
				if(response.success){
					alert("添加成功");
					$scope.entity={};
					editor.html("");
				}else{
					alert(response.message);
				}
			}		
		);				
	}

5、fastDFS入门小demo

(1)导入镜像

需要修改网关为仅主机,第一次开启时选择I move it

(2)导入入fastDFS的jar包,cmd窗口输入:

mvn install:install-file -Dfile=D:\JAVA\setup\fastdfs-1.2.jar -DgroupId=org.csource.fastdfs -DartifactId=fastdfs -Dversion=1.2 -Dpackaging=jar

mvn install:install-file -Dfile=D:\JAVA\setup\solr-solrj-4.10.4.jar -DgroupId=org.apache.solr -DartifactId=solr-solrj -Dversion=4.10.4 -Dpackaging=jar

(3)导入配置文件

(4)java类代码

public static void main(String[] args) throws Exception{
		//1、加载配置文件
ClientGlobal.init("D:\\JAVA\\workspace\\fastDFS\\src\\main\\resources\\fdfs_client.conf");
		//2、创建一个管理者客户端
		TrackerClient trackerClient = new TrackerClient();
		//3、连接管理者服务端
		TrackerServer trackerServer = trackerClient.getConnection();
		//4、声明存储服务器
		StorageServer storageServer = null;
		//5、获取存储服务器的客户端对象
		StorageClient storageClient2 = new StorageClient(trackerServer, storageServer);
		//6、上传文件
		String[] upload_file = storageClient2.upload_file("D:\\JAVA\\setup\\xuemei3.jpg", "jpg", null);
		for (String string : upload_file) {
			System.out.println(string);
			//group1/M00/00/00/wKgZhVvRi8yAbJJHAAD0RclXCSA283.jpg
		}
	}

6、商品录入-商品图片上传

(1)环境准备

​ 导入jar包依赖

 <dependencies>
   	<dependency> 
 	    <groupId>org.csource.fastdfs</groupId> 
 	    <artifactId>fastdfs</artifactId> 
 	</dependency> 
 	<dependency> 
 	 	<groupId>commons-fileupload</groupId> 
 	 	<artifactId>commons-fileupload</artifactId> 
	</dependency>
  </dependencies>

​ 配置多媒体解析器

<!-- 配置多媒体解析器 -->
	<bean id="multipartResolver" 
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">  
      	<!-- 文件上传上限 -->
	    <property name="maxUploadSize" value="1048576" />  
	</bean>

(2)Web层定义UploadController类:用来传输文件

@RestController
public class UploadController {

	@Autowired
	@Value("${FILE_SERVER_URL}")
	private String file_server_url;//读取ip地址
	
	@RequestMapping("/upload")
	public Result upload(MultipartFile file) {
		//获取文件的全文件名
		String originalFilename = file.getOriginalFilename();
		//获取文件名后缀
		String extName = originalFilename.substring(originalFilename.indexOf(".")+1);
		try {
			//读取配置文件
			util.FastDFSClient fastDFSClient = new FastDFSClient("/classpath:config/fdfs_client.conf");
			//获取上传文件id
			String fileId = fastDFSClient.uploadFile(file.getBytes(), extName);
			//拼接ip地址组合成url
			String url  = file_server_url+fileId;
			return new Result(true,url);
		} catch (Exception e) {
			e.printStackTrace();
			return new Result(false,"上传失败");
		}	
	}	
}

(3)前端:

​ 创建uploadService

app.service("uploadService",function($http){
	
	this.uploadFile=function(){
		var formData = new FormData();
		formData.append("file",file.files[0]);
		return $http({
			url:"../upload.do",//访问路径
			method:"post",//请求方法方式
			data:formData,//请求参数
			//指定上传格式,默认为json,设置后为multipart格式
			headers:{"Content-Type":undefined},
			//对表单进行二进制序列化
			transformRequest:angular.identity
		})
	}
})

​ GoodsController中调用uploadService:定义新增、删除、和保存方法

	//保存 
	$scope.add=function(){	
		//添加富文本框内容
		$scope.entity.tbGoodsDesc.introduction=editor.html();
		goodsService.add($scope.entity).success(
			function(response){
				if(response.success){
					alert("添加成功");
					$scope.entity={};
					editor.html("");
				}else{
					alert(response.message);
				}
			}		
		);				
	}

	//添加图片
	$scope.add_image_entity=function(){
		$scope.entity.tbGoodsDesc.item_images.push($scope.image_entity);
	}
	
	//删除图片
	$scope.remove_image_entity=function(index){
		$scope.entity.tbGoodsDesc.item_images.splice(index,1);
	}

(4)页面

​ 绑定显示数据

<div class="btn-group">
  <!-- 绑定点击事件初始化image_entity对象 -->
  <button type="button" class="btn btn-default" title="新建" data-target="#uploadModal"  data-toggle="modal" ng-click="image_entity={}" ><i class="fa fa-file-o"></i> 新建</button>
</div>

<table class="table table-bordered table-striped table-hover dataTable">
  <thead>
    <tr>
      <th class="sorting">颜色</th>
      <th class="sorting">图片</th>
      <th class="sorting">操作</th>
  </thead>
<tbody>
  <!-- 遍历tbGoodsDesc.item_images -->
  <tr ng-repeat="pojo in entity.tbGoodsDesc.item_images">					                        <td>
      {{pojo.color}}
    </td>
    <td>
      <img alt="" src="{{pojo.url}}" width="100px" height="100px">	            	 
    </td>
    <td> <button type="button" class="btn btn-default" title="删除" ng-click="remove_image_entity($index)"><i class="fa fa-trash-o"></i> 删除</button></td> 
  </tr>
  </tbody>

#【第六章】商品录入

###1、AngularJS实现三级联动(ng-options)

(1)选项商品分类

【思路逻辑】

使用到Angular的$watch,它的作用是监听变量的变动,当一级分类选项发生改变时,调用方法查询二级商品分类展示到页面,以此来实现三级或多级联动。

【代码实现】

<1>前端:

在goodsController中引入itemService,用以调用findByParentId()方法

	//查询一级目录
	$scope.selectItemCat1List=function(){
		itemCatService.findByParentId(0).success(
			function(response){
				$scope.ItemCat1List=response;
			}	
		)
	}
	
	//查询二级目录
	$scope.$watch("entity.tbGoods.category1Id",function(newValue,oldValue){
		itemCatService.findByParentId(newValue).success(
			function(response){
				$scope.ItemCat2List=response;
			}
		)
	})
	
	//查询三级目录
	$scope.$watch("entity.tbGoods.category2Id",function(newValue,oldValue){
		itemCatService.findByParentId(newValue).success(
			function(response){
				$scope.ItemCat3List=response;
			}
		)
	})

<2>【页面】:

用到ng-options

<table>
  <tr>
    <td>
      <select class="form-control" ng-model="entity.tbGoods.category1Id" ng-options="item.id as item.name for item in ItemCat1List">														
      </select>
    </td>
    <td>
      <select class="form-control select-sm" ng-model="entity.tbGoods.category2Id" ng-options="item.id as item.name for item in ItemCat2List"></select>
    </td>
    <td>
      <select class="form-control select-sm" ng-model="entity.tbGoods.category3Id" ng-options=" item.id as item.name for item in ItemCat3List"></select>
    </td>
    <td>
      模板ID:19
    </td>
  </tr>
</table>

(2)显示模板ID

【前端controller】,同三级分类

	//显示模板ID
	$scope.$watch("entity.tbGoods.category3Id",function(newValue,oldValue){
		itemCatService.findOne(newValue).success(
			function(response){
				$scope.entity.tbGoods.typeTemplateId=response.typeId;
			}
		)
	})

【html页面】:

<td>
  模板ID:{{entity.tbGoods.typeTemplateId}}
</td>00

(3)显示品牌下拉框和扩展属性

【前端controller】:由于brandIds在数据库是以字符串形式存储,需转换为JSON格式

//显示品牌下拉选项
	$scope.$watch("entity.tbGoods.typeTemplateId",function(newValue,oldValue){
		typeTemplateService.findOne(newValue).success(
			function(response){
				$scope.typeTemplate=response;
              //获取品牌json对象
				$scope.typeTemplate.brandIds = JSON.parse($scope.typeTemplate.brandIds);
              //获取扩展属性json对象
              $scope.entity.tbGoodsDesc.customAttributeItems  = JSON.parse(response.customAttributeItems);
			}
		)
		
	})

【html页面】:

<div class="col-md-2 title">品牌</div>
<div class="col-md-10 data">
  <select class="form-control" ng-model="entity.tbGoods.brandId" ng-options="brand.id as brand.text for brand in typeTemplate.brandIds"></select>
</div>

   <!--扩展属性-->
<div class="tab-pane" id="customAttribute">
  <div class="row data-type">     
    <!--  遍历扩展属性json对象  -->                           
    <div ng-repeat="item in entity.tbGoodsDesc.customAttributeItems">
      <div class="col-md-2 title">{{item.text}}</div>
      <div class="col-md-10 data">
        <input class="form-control" ng-model="item.value" placeholder="{{item.text}}">	         
      </div>
    </div>       
  </div>
</div>

【我的 BUG】:ng-options 写成ng-option

2、规格选择

(1)后端:

【逻辑思路】:在service层TypeTemplateService类中创建 List findSpecList(Long id)方法

通过id查询tb_type_template表,获取spec_ids字段,将其转换为Map对象存入List集合中,然后遍历集合,通过map存储的specId 查询tb_specification_option表,在每个Map对象中添加options属性并存入其中。

【代码实现】

<1>service层

@Autowired
	private TbSpecificationOptionMapper specificationOptionMapper;

@Override
public List<Map> findSpecList(Long templateId) {
  //根据模板ID查询模板对象
  TbTypeTemplate typeTemplate = typeTemplateMapper.selectByPrimaryKey(templateId);
  //将关联规格的json格式数据转换为List<Map>集合
  List<Map> specIds = JSON.parseArray(typeTemplate.getSpecIds(),Map.class);
  //遍历集合
  for (Map map : specIds) {
    //获取每个map中的id值查询规格选项表
    TbSpecificationOptionExample example = new TbSpecificationOptionExample();
    com.pinyougou.pojo.TbSpecificationOptionExample.Criteria createCriteria = example.createCriteria();
    createCriteria.andSpecIdEqualTo(new Long( (Integer)map.get("id")));
    //查询数据库,返回规格选项对象的集合,存如map集合中
    List<TbSpecificationOption> options = specificationOptionMapper.selectByExample(example);
    map.put("options", options);
  }
  return specIds;
}

我遇到的bug:TbSpecificationOptionMapper没有注入,报空指针异常

<2>Web层controller类

@RequestMapping("/findSpecList")
public List<Map> findSpecList(Long templateId) {
	return typeTemplateService.findSpecList(templateId);
}

(2)前端:

【逻辑思路】:集合结构为[{“attributeName”:“网络制式”,“attributeValue”:[“移动3G”,“移动4G”]}]

​ 假设定义,复选框所属的属性为key,复选框所代表的值为value,即key对应attributeName,value对应attributeValue。

​ (1)复选框的选中实现

​ 当点击复选框时,判断集合中有没有是否已经包含attributeName,若包含,将value存入attributeValue数组中,若不包含,将key,value键值对存入集合中;

​ (2)复选框的取消选中

​ 在点击复选框时

​ if(【进行判断复选框的勾选状态(传入$event参数)】){

​ 【若为选中,执行第(1)步】

​ }else{

​ 【若为未选中】

​ if(【判断要删除的数据是否为数组的唯一】){

​ 【删除数据是唯一值,用集合移除数组】

​ }else{

​ 【数据不是唯一值,用数组移除元素】

​ }

​ }

【代码实现】

baseController.js:定义公用方法,查询集合中是否存在某元素


【第七章】

1、商品列表

​ (1)显示列表:页面引入js文件,绑定数据

​ (2)商品的状态显示:

​ 定义一个数组$scope.status=[“未审核”,“已审核”,“已驳回”,“已关闭”];

​ 把状态值当作索引从数组中取出具体的值

​ (3)商品分类显示

​ 定义一个数组$scope.itemCatList=[];//商品分类列表

​ 调用itemCatService的findAll方法,把结果以id当索引存入集合中,页面再通过id取出

//商品状态
	$scope.status=["未审核","已审核","已驳回","已关闭"];
	//商品分类列表
	$scope.itemCatList=[];
	$scope.findItemCatList=function(){
		itemCatService.findAll().success(
			function(response){
				for (var i = 0; i < response.length; i++) {
					$scope.itemCatList[response[i].id]=response[i].name;
				}
			}
		)
	}

页面

 <tr ng-repeat="pojo in list">
   <td><input  type="checkbox"></td>			                              
   <td>{{pojo.sellerId}}</td>
   <td>{{pojo.goodsName}}</td>
   <td>{{pojo.price}}</td>
   <td>{{itemCatList[pojo.category1Id]}}</td>
   <td>{{itemCatList[pojo.category2Id]}}</td>
   <td>{{itemCatList[pojo.category3Id]}}</td>
   <td>
     <span>
       {{status[pojo.auditStatus]}}
     </span>
   </td>		                                  
   <td class="text-center">                                          
     <button type="button" class="btn bg-olive btn-xs">修改</button>                  
   </td>
</tr>

我出现的BUG:未初始调用 ng-init=findItemCatList()方法

(4)条件查询:绑定查询数据,调用reloadList()方法

   <div class="has-feedback">
     状态:<select ng-model="searchEntity.auditStatus">
     <option value="">全部</option>      
     <option value="0">未审核</option>    
     <option value="1">审核通过</option>    
     <option value="2">已驳回</option>    
     <option value="3">已通过</option>                                     
     </select>
     商品名称:<input ng-model="searchEntity.goodsName">									
     <button class="btn btn-default" ng-click="reloadList()">查询</button>                                    
</div>

2、商品修改

​ 【显示基本信息,图片,附加属性】:

​ (1)通过$location.search()[“id”]获取传递参数

​ (2)数据库存储json格式的字符串需要用JSON.parse()转换为对象

​ (3)扩展属性与新增的代码冲突,会重新对 s c o p e . e n t i t y . t b G o o d s D e s c . c u s t o m A t t r i b u t e I t e m s 赋 值 为 空 , 需 要 在 规 格 i d 变 量 监 控 方 法 中 加 个 判 断 i f ( scope.entity.tbGoodsDesc.customAttributeItems赋值为空,需要在规格id变量监控方法中加个判断if( scope.entity.tbGoodsDesc.customAttributeItemsidif(location.search()[“id”]==null)

​ 后端service

	/**
	 * 根据ID获取实体
	 * @param id
	 * @return
	 */
	@Override
	public Goods findOne(Long id){
		//创建组合实体类对象
		Goods goods = new Goods();
		//查询tbGoods并封装进组合实体类
		TbGoods tbGoods = goodsMapper.selectByPrimaryKey(id);
		goods.setTbGoods(tbGoods);
		//查询扩展表tbGoodsDesc
		TbGoodsDesc tbGoodsDesc = goodsDescMapper.selectByPrimaryKey(id);
		goods.setTbGoodsDesc(tbGoodsDesc);
		//查询扩展表itemsList
		TbItemExample example = new TbItemExample();
		com.pinyougou.pojo.TbItemExample.Criteria criteria = example.createCriteria();
		//添加goodsId条件
		criteria.andGoodsIdEqualTo(id);
		List<TbItem> itemsList = itemMapper.selectByExample(example);
		goods.setItemsList(itemsList);
		return goods;
	}

​ 前端controller

//查询实体 
$scope.findOne=function(){
  var id =$location.search()["id"];
  if(id==null){
    return ;
  }
  goodsService.findOne(id).success(
    function(response){
      $scope.entity= response;		
      //富文本框
      editor.html($scope.entity.tbGoodsDesc.introduction);
      //图片
      $scope.entity.tbGoodsDesc.itemImages=JSON.parse($scope.entity.tbGoodsDesc.itemImages);
      //附加属性
  $scope.entity.tbGoodsDesc.customAttributeItems=JSON.parse($scope.entity.tbGoodsDesc.customAttributeItems);
      //规格SKU列表数据
      for(var i=0;i<$scope.entity.itemsList.length;i++){
        $scope.entity.itemsList.spec[i]=JSON.parse($scope.entity.itemsList.spec[i])
      }
    }
  );				
}

【显示规格】

goodsController:调用searchObjectByKey()查询规格结果集合中是否存在对应的规格属性

//修改显示规格复选框
$scope.checkAttributeValue=function(specName,optionName){
  var items = $scope.entity.tbGoodsDesc.specificationItems;
  var object = $scope.searchObjectByKey(items,"attributeName",specName);
  //判断集合中是否能查询出对应的规格属性
  if(object!=null){
    if(object.attributeValue.indexOf(optionName)>0){
      //有勾选规格
      return true;
    }else{
      //没有勾选规格
      return false;
    }
  }else{
    //集合没有对象的规格属性
    return false;
  }
}

html页面调用ng-checked

<div>
  <div class="col-md-2 title">{{pojo.text}}</div>
  <div class="col-md-10 data" >
    <span ng-repeat="option in pojo.options">
      <input  type="checkbox" 
            ng-click="updateSpecAttribute($event,pojo.text,option.optionName);createItemsList()"
			ng-checked="checkAttributeValue(pojo.text,option.optionName)"
       >{{option.optionName}}	       	
    </span>  	
  </div>
</div>   

3、商品保存

(1)后端

​ 【代码逻辑】

​ [ 1 ] 在service层:goods组合实体类共涉及三张表,一张goods基本表,一张goodsDesc扩展表,一张Item扩展表

​ goods基本表和goodsDesc扩展表直接调用update修改

​ 由于item扩展表无法确定是添加还是减少数据,需要先删除再存储。

​ [ 2 ] 在controller层判断传入的goods所属商家是否和当前登录商家ID所匹配

4、运营商后台-商品审核

5、注解事务配置

加入applicationContext-tx.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:tx="http://www.springframework.org/schema/tx"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 

    <!-- 事务管理器  -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource"/>  
    </bean>  
      
    <!-- 开启事务控制的注解支持 -->  
    <tx:annotation-driven transaction-manager="transactionManager"/>
   
</beans>

【第八章】广告管理与缓存

1、广告管理

(1)广告图片上传

​ 拷贝资源:UploadController.java 、 uploadService.js 、fdfs_client.conf

​ springMVC.xml多媒体解析器

	<!-- 配置多媒体解析器 -->
	<bean id="multipartResolver" 
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">  
		<property name="defaultEncoding" value="UTF-8"></property>
	    <property name="maxUploadSize" value="1048576" />  
	</bean>

​ 前端contentController.js修改

//上传文件
	$scope.upload=function(){
		uploadService.uploadFile().success(
			function(response){
				if(response.success){
					$scope.entity.pic=response.message;
				}else{
					alert(response.message);
				}
			}
		)
	}

​ content.html页面修改

<tr>
  <td>图片</td>
  <td>
    <input type="file" id="file">
    <button  ng-click="upload()">上传</button>
    <img src="{{entity.pic}}" width="200px" height="100px"></img>
</tr>
(2)广告类目管理

​ 定义变量contentCategoryList,查询广告类目封装进去,页面使用ng-options显示,需要注意的是页面引入service服务

​ 前端contentController.js修改

//查询类目条目
$scope.findContentCategoryList=function(){
		contentCategoryService.findAll().success(
			function(response){
              //注意变量名不要定义重复
				$scope.contentCategoryList=response;
			}
		);
	}

​ html页面修改

<tr>
  <td>内容类目</td>
  <td>
    <select class="form-control" ng-model="entity.categoryId" 
            ng-options="item.id as item.name for item in contentCategoryList"/>
  </td>
</tr>

(3)广告状态、图片显示

​ 页面修改

<tr>
  <td>状态</td>
  <td><input type="checkbox"  ng-model="entity.status"
             placeholder="状态" ng-true-value="1" ng-false-value="2"></td>
</tr>


<td>
  <img src="{{entity.pic}}" width="100px" height="50px"></img>
</td>

angularJS表达式在src中需要加括号

2、广告展示

方法名:findByCategoryId(Long id)

需要添加的条件有:根据id查询,状态必须为有效,按顺序排序

service层

    /**
	 * 根据id查询广告
	 */
    @Override
    public List<TbContent> findByCategoryId(Long categoryId) {

        TbContentExample example = new TbContentExample();
        Criteria criteria = example.createCriteria();
        criteria.andCategoryIdEqualTo(categoryId);//根据id查询
        criteria.andStatusEqualTo("1");//状态为有效
        example.setOrderByClause("sort_order");//排序
        List<TbContent> list = contentMapper.selectByExample(example);
        return list;
  }

前端变量定义:

定义广告数组$scope.contentList=[ ];以categoryId为角标将查询的集合存入里面

前端controller层

app.controller("contentController",function($scope,contentService){
	
	//初始化定义集合
	$scope.contentList=[];
	
	//根据id查询广告
	$scope.findByCategoryId=function(categoryId){
		contentService.findByCategoryId(categoryId).success(
			function(response){
				$scope.contentList[categoryId]=response;
			}	
		);
	}

})

前端页面修改

<!--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}}" ng-repeat="item in contentList[1]" class="{{$index==0?'active':''}}"></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>

  </div><a href="#myCarousel" data-slide="prev" class="carousel-control left"></a><a href="#myCarousel" data-slide="next" class="carousel-control right"></a>
</div>

通过三元表式来定义active属性:class="{{$index==0?‘active’:’ ‘}}"

3、广告数据的缓存

(1)加入缓存

​ 导入相关的依赖

 <!-- 缓存 --> 
 	<dependency>  
 	 	  <groupId>redis.clients</groupId>  
 	 	  <artifactId>jedis</artifactId>  
 	</dependency>  
 	<dependency>  
 	 	  <groupId>org.springframework.data</groupId>  
 	 	  <artifactId>spring-data-redis</artifactId>  
 	</dependency>

​ 在服务层添加缓存

/**
* 根据id查询广告
*/
@Override
public List<TbContent> findByCategoryId(Long categoryId) {

  List<TbContent> list = (List<TbContent>)redisTemplate.boundHashOps("content").get(categoryId);

  if(list==null) {
      //查询数据库
      System.out.println("从数据库中查询");
      TbContentExample example = new TbContentExample();
      Criteria criteria = example.createCriteria();
      criteria.andCategoryIdEqualTo(categoryId);//根据id查询
      criteria.andStatusEqualTo("1");//状态为有效
      example.setOrderByClause("sort_order");//排序
      list = contentMapper.selectByExample(example);
      redisTemplate.boundHashOps("content").put(categoryId, list);
  }else {
      //查询缓存
      System.err.println("从缓存中查询");
  }
      return list;
}
(2)更新缓存

​ 在增删改的时候对缓存进行清除

​ 增加:直接删缓存

	/**
	 * 增加
	 */
	@Override
	public void add(TbContent content) {
		contentMapper.insert(content);	
		//删除缓存
		redisTemplate.boundHashOps("content").delete(content.getCategoryId());
	}

​ 删除:需要在删除语句前清除缓存

/**
 * 批量删除
 */
@Override
public void delete(Long[] ids) {
	for(Long id:ids){
		//通过id查询categoryId值
		Long categoryId = contentMapper.selectByPrimaryKey(id).getCategoryId();
		//删除缓存
		redisTemplate.boundHashOps("content").delete(categoryId);
		//删除数据
		contentMapper.deleteByPrimaryKey(id);
	}		
}

​ 修改:先查修改前所属categoryId,然后获取传参过来的categoryId,判断两者是否相同,相同则说明未修改所属分组,只用清除一次。不相同则需要分别清理。

	/**
	 * 修改
	 */
	@Override
	public void update(TbContent content){
		//查询修改前的所属分组id
		Long categoryId = contentMapper.selectByPrimaryKey(content.getId()).getCategoryId();
		//清除缓存
		redisTemplate.boundHashOps("content").delete(categoryId);
		//修改数组
		contentMapper.updateByPrimaryKey(content);
		//获取修改后的所属分组id
		if(categoryId!=content.getCategoryId()) {
			//分组id被修改,清除修改后所属分组的缓存
			redisTemplate.boundHashOps("content").delete(content.getCategoryId());
		}
	}	

【第九章】

1、solr的部署

(1)安装tomcat(注意与jdk版本的冲突问题)

(2)将solr的war包导入tomcat的webapp目录下

(3)启动tomcat解压war包

(4)将solr的example\lib\ext目录下的扩展jar包导入tomcat项目webapp/lib目录下

(5)配置solr的example\solr目录拷出来取名solrhome作为

【第十章】

1、高亮显示

​ 后端service

​ (1)用 solrTemplate调用queryForHighlightPage(query,
TbItem.class)方法,需要query对象

​ (2)创建高亮query对象,选择SimpleHighlightQuery实现类

​ (3)设置高亮选项:

​ query.setHighlightOptions(highlightOptions),需要高亮选项对象

​ (4)创建高亮选项highlightOptions对象,并设置高亮域,addField(“item_title”)

​ (5)设置高亮前缀和后缀

​ highlightOptions.setSimplePrefix("");//高亮前缀

​ highlightOptions.setSimplePostfix("");//高亮后缀

​ (6)按照关键字查询

Criteria criteria=new
Criteria(“item_keywords”).is(searchMap.get(“keywords”));
query.addCriteria(criteria);

HighlightPage page = solrTemplate.queryForHighlightPage(query, TbItem.class)

​ (7)循环高亮入口

​ (8)判定高亮内容是否为空,不为空将其存储到实例对象中

​ (9)返回结果集

/**
	 * 根据关键字搜索列表
	 * @param searchMap
	 * @return
	 */
	private Map  searchList(Map searchMap) {
		Map map = new HashMap();
		
		HighlightQuery query = new SimpleHighlightQuery();
		//设置高亮域
		HighlightOptions highlightOptions = new HighlightOptions();
		highlightOptions.addField("item_title");
		//设置高亮前缀
		highlightOptions.setSimplePrefix("<em style='color:red'>");
		//设置高亮后缀
		highlightOptions.setSimplePostfix("</em>");
		//设置高亮选项
		query.setHighlightOptions(highlightOptions);
		//按照关键字查询
		Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
		query.addCriteria(criteria );
		//执行查询
		HighlightPage<TbItem> page = solrTemplate.queryForHighlightPage(query , TbItem.class);
		
		//遍历高亮入口集合
		for (HighlightEntry<TbItem> h : page.getHighlighted()) {
			//获取原实体类
			TbItem item = h.getEntity();
			if(h.getHighlights().size()>0&&h.getHighlights().get(0).getSnipplets().size()>0) {
				//设置高亮结果
				item.setTitle(h.getHighlights().get(0).getSnipplets().get(0));
			}
		}
		
		map.put("rows", page.getContent());
		
		return map;
	}

​ 前端js

​ 由于AngularJS有防HTML攻击,需对高亮标签添加信任,配置一个过滤器,运用到$sce.trustAsHtml();

app.filter("trustHtml",['$sce',function($sce){
	return function(data){
		return $sce.trustAsHtml(data);
	}
}])

​ html页面修改

​ 用ng-bind-html

<div class="attr" ng-bind-html="item.title | trustHtml"></div>

【十一章】

1、价格区间刷选

【业务逻辑】

2、分页

后端:【业务逻辑】

​ (1)初始定义分页属性,当前页pageNo,每页显示条目数pageSize

​ (2)分页查询,query.setOffset( arg0);//设置起始索引

​ query.setrows(); //设置每页条目数

​ 返回值添加总页数totalPage、总记录数

前端:

​ (1)构建页码数组pageLaber

3、多关键字查询

​ 去掉用户搜索输入的多余关键字

4、排序

【代码逻辑】:前端传递sort(排序的值:升序还是降序) 和sortField(排序的字段)

5、搜索页首页对接

【代码逻辑】:首页搜索框定义变量ng-model=“keywords”,通过location传递给搜索页面,搜索页面再通过$location.search()方法接受参数,并初始化赋值

【注意事项】

​ [ 1 ]传参的时候需要用#?

​ [ 2 ]接收参数时使用$location对象而不是location

​ [ 3 ]接收参数前需初始调用(接收参数)方法,接收参数后需调用查询方法

6、商品审核时将数据加入solr库

【代码逻辑】:在商品审核时调用seller-goods-service的goodsService,通过Long[ ] ids的值查询出审核商品所有的SKU对象集合,判断结果集是否有查到数据,查到则存入solr库中。

【个人理解】:觉得需要添加商品上架审核,在上架审核中添加到solr库,而不是在商品审核中,由于实现原理类似,不在此过多纠结

【十二章】freemark

建议学习:themeleaf

1、入门demo指令

​ (1)#assign:在页面定义变量

    <#-- [1]#assign:在页面定义变量 -->
    <#assign linkman="李四">
    ${linkman}<br>

​ (2)#include:用于模板的嵌套

    <#include "head.ftl“>

​ (3)if:条件判断

    <#if success=true> 
      你已通过实名认证
    <#else> 
        你未通过实名认证
    </#if>

​ (4)list:循环

	<#-- [4]#list:循环 -->
	<#list goodsList as goods>
		${goods.name}  ${goods.price}  <br>
	</#list>

​ (5)内置函数:

​ ?size :集合大小

​ ?eval:将json字符串转换为对象

2、配置代码

public static void main(String[] args) throws Exception {
		//1、创建一个配置对象
		Configuration configuration = new Configuration(Configuration.getVersion());
		//2.设置模板所在目录
		configuration.setDirectoryForTemplateLoading(new File("D:\\JAVA...."));
		//3.设置默认编码集
		configuration.setDefaultEncoding("UTF-8");
		//4.获取模板对象
		Template template = configuration.getTemplate("test.ftl");
		//5.设置数据类型
  
		.....
		
		//6.创建输出流对象
		Writer out = new FileWriter("d:\\test.html");
		//7.输出
		template.process(map, out);
		//8.释放资源
		out.close();
		System.err.println("页面已生成");
	}

整合到spring需要配置FreeMarkerConfigurer的 bean对象替换原先的配置对象

 <!-- 配置属性 -->
	<bean id="freemarkerConfig" 
		class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
		<!-- 配置模板所在目录 -->
		<property name="templateLoaderPath" value="/WEB-INF/ftl/"/>
		<!-- 配置默认编码集 -->
		<property name="defaultEncoding" value="UTF-8" />
	</bean>

【总结】:freemaker生成静态页面

​ 构建一个模板页面,在配置文件中配置[1].整合spring的Bean对象,[2]保存页面生成的目录(.properties文件),在后端service层通过数据库查询出页面需要的数据对象,在模板页面中进行调用,前端可以整合AngularJS实现一些页面交互效果。

​ 涉及功能:

​ (1)页面基本数据展示:

​ 包括商品名称、商品价格、商品卖点、商品描述、商品扩展属性等等基本数据,这一类数据的特点是都为基本类型数据(字符串或数值,在页面展示时不需要循环遍历获取子属性),通过在后端service层查询数据库赋好值,在页面直接通过指令调用

​ (2)商品sku规格展示:

​ 所有数据:goodsDesc表中的specification_items字段

​ 数据格式:[{“attributeName”:“网络制式”,“attributeValue”:[“移动3G”,“移动4G”]},{“attributeName”:“屏幕尺寸”,“attributeValue”:[“6寸”,“5.5寸”]}]

​ 因为在数据是以JSON类型字符串存储的,需要转换为对象后才能在页面进行显示。

​ 所以在页面定义了变量specificationList:用于接受该JSON字符串转换为对象后的值(转换可在前端也可在后端);

​ 在页面展示时用到双层嵌套循环,第一层循环JSON对象,遍历获取规格名称attributeName的值,第二层循环attributeValue获取每个规格属性的值。

​ 显示效果:

​ 网络制式 : 移动3G 移动4G

​ 屏幕尺寸 : 6寸 5.5寸

​ (3)规格的数据绑定,以及选择效果切换:

​ 需求:当点击规格按钮时出现特定的效果显示。

​ 数据绑定:定义变量$scope.specificationItems={};定义一个方法,当点击规格时,将规格所属的值赋值给specificationItems,通过点击按钮改变变量的值来实现绑定。

//选择规格时给传参对象赋值
	$scope.selectSpecification=function(key,value){
		$scope.specificationItems[key]=value;
		//改变sku(后面的逻辑)
		$scope.search();
	}

​ 选择效果切换:定义一个方法,传入规格按钮所属的固定的值,然后将其与specificationItems进行对比,若两者相同,说明该按钮为点击按钮,在class=”“中调用并赋予select特效。

//判断规格是否被选中
	$scope.isSelected=function(key,value){
		if($scope.specificationItems[key]==value){
			return true;
		}else{
			return false;
		}
	}

​ (4)默认sku显示

​ 需求:当首次进入页面,显示该SPU商品的默认SKU。

​ 实现:在后端查询item表时通过是否默认进行倒叙DESC排序,这样查询出来的itemList集合,0号索引即为默认SKU,定义一个方法将itemList[0]赋值给specificationItems对象,这样规格按钮会通过(3)中的方法自动判定是否应该被勾选。

//加载sku数据
	$scope.loadSku=function(){		
		$scope.sku = skuList[0];
		$scope.specificationItems=JSON.parse(JSON.stringify($scope.sku.spec));
	}

​ 需求:在切换规格时,同时改变SKU的其他属性(价格,商品名称等)

​ 实现:在页面加载时,将该商品的所有SKU对象存入skuList集合中,在点击规格按钮时,同时判定所选的SKU(specificationItems对象)是否存在skuList中,若存在改变页面SKU显示数据。

​ 方法实现逻辑:

//选择规格时,改变商品参数显示
	$scope.search=function(){
		for(i=0;i<skuList.length;i++){
			var flag = $scope.matchObject(skuList[i].spec,$scope.specificationItems);
			if(flag){
				$scope.sku = skuList[i];
				return ;
			}
		}
	}

//匹配两集合的值是否相等
	$scope.matchObject=function(map1,map2){
		//遍历map1匹配map2
		for(var key in map1){
			if(map1[key]!=map2[key]){
				return false;
			}
		}
		//遍历map2匹配map1
		for(var key in map2){
			if(map2[key]!=map1[key]){
				return false;
			}
		}
		return true;
	}

​ (5)购买数量的加减:逻辑略,

【未实现的功能】

​ 当用户不是通过‘+’ - 按钮来增加,而是直接在文本框输入值来购买时,需要对文本框输入的值进行判定,如果小于1或者大于200,将其赋予范围内的值。

//增加和减少购买数量
	$scope.addNum=function(value){
		$scope.num=$scope.num+value;
		if($scope.num<1){
			$scope.num=1;
		}
		if($scope.num>200){
			$scope.num=200;
		}
	}
	

​ 【需要注意的点】:

​ (1)页面名称以商品id命名;

​ (2)当模板页面需要引入(json格式)数组对象,且其中有数值类型的值时,值后面需要加?c来取消逗号,以免破坏数据格式;

​ (3)在生成规格列表时,其中调用方法的传递参数需要用引号引起来,来实现传递固定的静态值。简单来说,在生成规格选项列表时,我们用的是遍历,其中绑定的值都是变量,但静态页面中每个选项传递的参数都是特定的,需要将变量加上引号将其变成固定值

【十三章】JMS,ActiveMQ

1、点对点、发布和订阅

(1)原生方式实现

提供者

public static void main(String[] args) throws JMSException {
		//1.创建工厂对象
		ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.25.128:61616");
		//2.获取连接对象
		Connection connection = connectionFactory.createConnection();
		//3.开启连接
		connection.start();
		//4.获取session会话对象
		Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
  
		//5.创建消息发布者
  		//Quene quene = session.createQuene("text-quene");
		Topic topic = session.createTopic("text-topic");	
		//6.创建消息生产者
  		//MessageProducer producer = session.createProducer(quene)
		MessageProducer producer = session.createProducer(topic);
  
		//7.创建消息文本信息
		TextMessage textMessage = session.createTextMessage("这是一条群发消息");
		//8.发布消息
		producer.send(textMessage);
		//9.关闭资源
		producer.close();
		session.close();
		connection.close();
		
		System.out.println("已发布群发消息");

	}

​ 消费者

public static void main(String[] args) throws JMSException, IOException {
		//1.创建工厂对象
		ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.25.128:61616");
		//2.获取连接对象
		Connection connection = connectionFactory.createConnection();
		//3.开启连接
		connection.start();
		//4.获取session会话对象
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
  
		//5.创建主题对象
  		//Quene quene = session.createQuene("text-quene");
		Topic topic = session.createTopic("text-topic");
		//6.创建消息订阅者对象
  		//MessageProducer producer = session.createProducer(quene)
		MessageConsumer consumer = session.createConsumer(topic);
  
		//7.设置监听
		consumer.setMessageListener(new MessageListener() {		
			public void onMessage(Message message) {
				TextMessage textMessage = (TextMessage)message;
				try {
					System.out.println("接受到消息:"+textMessage.getText());
				} catch (JMSException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}			
			}
		});
		System.out.println("订阅者已就绪");
		//8.等待键盘输入
		System.in.read();
		//9.释放资源
		consumer.close();
		session.close();
		connection.close();
		
	}

(2)spring整合

【十四章】短信验证-阿里大于

【实现思路】

​ (1)用户在注册页面申请账号,输出用户名,密码,手机号后点击发送短信验证,这时发请求到后端

​ 后端:【实现发短信】
​ (2)后端接受到请求后首先随机生成一个6位数验证码,然后发送点对点消息到activeMQ中间件

​ 消息采用mapMessage类型,存储[1]手机号,[2]验证码、[3]阿里大于签名、[4]模板id

​ (3)需要注意的是在生成验证码的的时候需要将验证码存储到redis中,采用hash存储,大KEY为固定的“sms”,小key为手机号码。值为生成的验证码

​ (4)在短信接受的服务端pinyougou-sms-service,创建一个监听类,读取到消息参数

​ (5)调用发短信服务的工具类发送短信

​ (6)用户收到短信,输入短信验证码,提交注册请求

​ 这里传递的参数有:[1]用户名,[2]密码,[3]手机号,[4]验证码

​ (7)【校验两个密码文本框是否一样】通过分别给两个密码文本框绑定不同的变量名,在前端controller中先判断这两个密码变量是否相等,若相等发送请求到后端,若不相等,弹出错误提示框return。

​ 后端:

​ (8)【实现校验验证码】后端接受到请求后

​ 从redis中用手机号做小key取出缓存中验证码,比较两者是否相等

​ 【注】:为了程序的健壮性,需先对其中一个进行非空判定,再进行比较,避免空指针异常

​ (9)【判定用户名是否存在】校验成功后,查询数据库,判断用户名是否已存在,若已存在,返回信息到前台,若用户名可用进行下一步

​ (10)【对密码进行加密】使用md5加密

​ (11)【存储数据】将数据存储到数据库

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值