• 从0开始独立完成企业级Java电商网站开发(服务端)


    数据表结构设计

    唯一索引unique,保证数据唯一性

    CREATE TABLE `mmall_user` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户表id',
      `username` varchar(50) NOT NULL COMMENT '用户名',
      `password` varchar(50) NOT NULL COMMENT '用户密码,MD5加密',
      `email` varchar(50) DEFAULT NULL,
      `phone` varchar(20) DEFAULT NULL,
      `question` varchar(100) DEFAULT NULL COMMENT '找回密码问题',
      `answer` varchar(100) DEFAULT NULL COMMENT '找回密码答案',
      `role` int(4) NOT NULL COMMENT '角色0-管理员,1-普通用户',
      `create_time` datetime NOT NULL COMMENT '创建时间',
      `update_time` datetime NOT NULL COMMENT '最后一次更新时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `user_name_unique` (`username`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
    
    

    单索引及组合索引

    CREATE TABLE `mmall_order_item` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单子表id',
      `user_id` int(11) DEFAULT NULL,
      `order_no` bigint(20) DEFAULT NULL,
      `product_id` int(11) DEFAULT NULL COMMENT '商品id',
      `product_name` varchar(100) DEFAULT NULL COMMENT '商品名称',
      `product_image` varchar(500) DEFAULT NULL COMMENT '商品图片地址',
      `current_unit_price` decimal(20,2) DEFAULT NULL COMMENT '生成订单时的商品单价,单位是元,保留两位小数',
      `quantity` int(10) DEFAULT NULL COMMENT '商品数量',
      `total_price` decimal(20,2) DEFAULT NULL COMMENT '商品总价,单位是元,保留两位小数',
      `create_time` datetime DEFAULT NULL,
      `update_time` datetime DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `order_no_index` (`order_no`) USING BTREE,
      KEY `order_no_user_id_index` (`user_id`,`order_no`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=135 DEFAULT CHARSET=utf8;
    
    

    查业务问题的后悔药

    create_time 数据创建时间
    update_time 数据更新时间

    mybatis三剑客

    mybatis-generator自动化生成数据库交互代码

    配置pom.xml

    <build>
        <finalName>mmall</finalName>
        <plugins>
          <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.2</version>
            <configuration>
              <verbose>true</verbose>
              <overwrite>true</overwrite>
            </configuration>
          </plugin>
      </build>
    
    

    datasource.properties

    db.driverLocation=/Users/imooc/mysql-connector-java-5.1.6-bin.jar
    db.driverClassName=com.mysql.jdbc.Driver
    
    #db.url=jdbc:mysql://192.1.1.1:3306/mmall?characterEncoding=utf-8
    db.url=jdbc:mysql://你的数据库IP:你的数据库Port/你的database?characterEncoding=utf-8
    db.username=mmall
    db.password=dbpassword
    
    
    db.initialSize = 20
    db.maxActive = 50
    db.maxIdle = 20
    db.minIdle = 10
    db.maxWait = 10
    db.defaultAutoCommit = true
    db.minEvictableIdleTimeMillis = 3600000
    
    
    

    generatorConfig.xml

    <?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>
        <!--导入属性配置-->
        <properties resource="datasource.properties"></properties>
    
        <!--指定特定数据库的jdbc驱动jar包的位置-->
        <classPathEntry location="${db.driverLocation}"/>
    
        <context id="default" targetRuntime="MyBatis3">
    
            <!-- optional,旨在创建class时,对注释进行控制 -->
            <commentGenerator>
                <property name="suppressDate" value="true"/>
                <property name="suppressAllComments" value="true"/>
            </commentGenerator>
    
            <!--jdbc的数据库连接 -->
            <jdbcConnection
                    driverClass="${db.driverClassName}"
                    connectionURL="${db.url}"
                    userId="${db.username}"
                    password="${db.password}">
            </jdbcConnection>
    
    
            <!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
            <javaTypeResolver>
                <property name="forceBigDecimals" value="false"/>
            </javaTypeResolver>
    
    
            <!-- Model模型生成器,用来生成含有主键key的类,记录类 以及查询Example类
                targetPackage     指定生成的model生成所在的包名
                targetProject     指定在该项目下所在的路径
            -->
            <!--<javaModelGenerator targetPackage="com.mmall.pojo" targetProject=".srcmainjava">-->
            <javaModelGenerator targetPackage="com.mmall.pojo" targetProject="./src/main/java">
                <!-- 是否允许子包,即targetPackage.schemaName.tableName -->
                <property name="enableSubPackages" value="false"/>
                <!-- 是否对model添加 构造函数 -->
                <property name="constructorBased" value="true"/>
                <!-- 是否对类CHAR类型的列的数据进行trim操作 -->
                <property name="trimStrings" value="true"/>
                <!-- 建立的Model对象是否 不可改变  即生成的Model对象不会有 setter方法,只有构造方法 -->
                <property name="immutable" value="false"/>
            </javaModelGenerator>
    
            <!--mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 -->
            <!--<sqlMapGenerator targetPackage="mappers" targetProject=".srcmain
    esources">-->
            <sqlMapGenerator targetPackage="mappers" targetProject="./src/main/resources">
                <property name="enableSubPackages" value="false"/>
            </sqlMapGenerator>
    
            <!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码
                    type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
                    type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
                    type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
            -->
    
            <!-- targetPackage:mapper接口dao生成的位置 -->
            <!--<javaClientGenerator type="XMLMAPPER" targetPackage="com.mmall.dao" targetProject=".srcmainjava">-->
            <javaClientGenerator type="XMLMAPPER" targetPackage="com.mmall.dao" targetProject="./src/main/java">
                <!-- enableSubPackages:是否让schema作为包的后缀 -->
                <property name="enableSubPackages" value="false" />
            </javaClientGenerator>
    
    
            <table tableName="mmall_shipping" domainObjectName="Shipping" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
            <table tableName="mmall_cart" domainObjectName="Cart" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
            <table tableName="mmall_cart_item" domainObjectName="CartItem" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
            <table tableName="mmall_category" domainObjectName="Category" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
            <table tableName="mmall_order" domainObjectName="Order" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
            <table tableName="mmall_order_item" domainObjectName="OrderItem" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
            <table tableName="mmall_pay_info" domainObjectName="PayInfo" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
            <table tableName="mmall_product" domainObjectName="Product" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
                <columnOverride column="detail" jdbcType="VARCHAR" />
                <columnOverride column="sub_images" jdbcType="VARCHAR" />
            </table>
            <table tableName="mmall_user" domainObjectName="User" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
    
    
            <!-- geelynote mybatis插件的搭建 -->
        </context>
    </generatorConfiguration>
    
    

    运行

    mybatis-plugin idea插件,实现mybatis的接口文件和xml自动跳转

    下载

    mybatis-pagehelper mybatis分页组件

    配置pom.xml

    	<!-- mybatis pager -->
    
        <dependency>
          <groupId>com.github.pagehelper</groupId>
          <artifactId>pagehelper</artifactId>
          <version>4.1.0</version>
        </dependency>
    
        <dependency>
          <groupId>com.github.miemiedev</groupId>
          <artifactId>mybatis-paginator</artifactId>
          <version>1.2.17</version>
        </dependency>
    
        <dependency>
          <groupId>com.github.jsqlparser</groupId>
          <artifactId>jsqlparser</artifactId>
          <version>0.9.4</version>
        </dependency>
    
    
    

    Tomcat加载spring、springmvc

    方式一:配置web.xml

    	<?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns="http://java.sun.com/xml/ns/javaee"
    	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    	id="WebApp_ID" version="2.5">
    
    
    	<welcome-file-list>
    		<welcome-file>login.jsp</welcome-file>
    	</welcome-file-list>
    	
    	<!-- 启动我们自己的listener -->
    	<listener>
    		<listener-class>com.atguigu.scw.manager.listener.MyAppListener</listener-class>
    	</listener>
    
    	<!-- 启动spring容器 -->
    	<!-- needed for ContextLoaderListener -->
    	<context-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>classpath:spring-*.xml</param-value>
    	</context-param>
    
    	<!-- Bootstraps the root web application context before servlet initialization -->
    	<listener>
    		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    	</listener>
    
    
    	<!-- The front controller of this Spring Web application, responsible for 
    		handling all application requests -->
    	<servlet>
    		<servlet-name>springDispatcherServlet</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<param-value>classpath:springmvc.xml</param-value>
    		</init-param>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    
    	<!-- Map all requests to the DispatcherServlet for handling -->
    	<servlet-mapping>
    		<servlet-name>springDispatcherServlet</servlet-name>
    		<url-pattern>/</url-pattern>
    	</servlet-mapping>
    
    	<!-- 加上字符编码过滤器 -->
    	<filter>
    		<filter-name>CharacterEncodingFilter</filter-name>
    		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    		<!-- 只是指定了编码格式 -->
    		<init-param>
    			<param-name>encoding</param-name>
    			<param-value>utf-8</param-value>
    		</init-param>
    		<!-- 进行请求乱码解决 -->
    		<init-param>
    			<param-name>forceRequestEncoding</param-name>
    			<param-value>true</param-value>
    		</init-param>
    		<init-param>
    			<param-name>forceResponseEncoding</param-name>
    			<param-value>true</param-value>
    		</init-param>
    	</filter>
    	<filter-mapping>
    		<filter-name>CharacterEncodingFilter</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>
    
    </web-app>
    
    

    方式二:配置web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://java.sun.com/xml/ns/javaee"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
             id="WebApp_ID" version="2.5">
    
        <display-name>Archetype Created Web Application</display-name>
    
        <filter>
            <filter-name>characterEncodingFilter</filter-name>
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <param-name>encoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
            <init-param>
                <param-name>forceEncoding</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>characterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    
        <listener>
            <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
        </listener>
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                classpath:applicationContext.xml
            </param-value>
        </context-param>
    
        <servlet>
            <servlet-name>dispatcher</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>dispatcher</servlet-name>
            <url-pattern>*.do</url-pattern>
        </servlet-mapping>
    
    </web-app>
    
    
    

    测试插件

    FeHelper

    Restlet Client

    用户模块

    横向越权

    入参需要指定当前用户id

    纵向越权

    MD5明文加密

    盐值加密

    MD5Util.java

    package com.mmall.util;
    
    import org.springframework.util.StringUtils;
    
    import java.security.MessageDigest;
    
    /**
     * Created by geely
     */
    public class MD5Util {
    
        private static String byteArrayToHexString(byte b[]) {
            StringBuffer resultSb = new StringBuffer();
            for (int i = 0; i < b.length; i++)
                resultSb.append(byteToHexString(b[i]));
    
            return resultSb.toString();
        }
    
        private static String byteToHexString(byte b) {
            int n = b;
            if (n < 0)
                n += 256;
            int d1 = n / 16;
            int d2 = n % 16;
            return hexDigits[d1] + hexDigits[d2];
        }
    
        /**
         * 返回大写MD5
         *
         * @param origin
         * @param charsetname
         * @return
         */
        private static String MD5Encode(String origin, String charsetname) {
            String resultString = null;
            try {
                resultString = new String(origin);
                MessageDigest md = MessageDigest.getInstance("MD5");
                if (charsetname == null || "".equals(charsetname))
                    resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
                else
                    resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
            } catch (Exception exception) {
            }
            return resultString.toUpperCase();
        }
    
        public static String MD5EncodeUtf8(String origin) {
            origin = origin + PropertiesUtil.getProperty("password.salt", "");
            return MD5Encode(origin, "utf-8");
        }
    
    
        private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
                "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
    
    }
    
    
    

    PropertiesUtil.java

    读取src/main/resources目录下的配置文件

    package com.mmall.util;
    
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.Properties;
    
    /**
     * Created by geely
     */
    public class PropertiesUtil {
    
        private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);
    
        private static Properties props;
    
        static {
            String fileName = "mmall.properties";
            props = new Properties();
            try {
                props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
            } catch (IOException e) {
                logger.error("配置文件读取异常",e);
            }
        }
    
        public static String getProperty(String key){
            String value = props.getProperty(key.trim());
            if(StringUtils.isBlank(value)){
                return null;
            }
            return value.trim();
        }
    
        public static String getProperty(String key,String defaultValue){
    
            String value = props.getProperty(key.trim());
            if(StringUtils.isBlank(value)){
                value = defaultValue;
            }
            return value.trim();
        }
    
    }
    
    
    

    mmall.properties

    ftp.server.ip=你的FTP服务器ip地址
    ftp.user=mmallftp
    ftp.pass=ftppassword
    ftp.server.http.prefix=http://img.happymmall.com/
    
    
    alipay.callback.url=http://www.happymmall.com/order/alipay_callback.do
    
    password.salt = geelysdafaqj23ou89ZXcj@#$@#$#@KJdjklj;D../dSF.,
    
    
    

    应用:明文加密

    guava缓存

    设置token并传给前台用户

    设置token并传给前台用户

    验证token

    TokenCache.java

    package com.mmall.common;
    
    import com.google.common.cache.CacheBuilder;
    import com.google.common.cache.CacheLoader;
    import com.google.common.cache.LoadingCache;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * Created by geely
     */
    public class TokenCache {
    
        private static Logger logger = LoggerFactory.getLogger(TokenCache.class);
    
        public static final String TOKEN_PREFIX = "token_";
    
        //LRU算法
        private static LoadingCache<String,String> localCache = CacheBuilder.newBuilder().initialCapacity(1000).maximumSize(10000).expireAfterAccess(12, TimeUnit.HOURS)
                .build(new CacheLoader<String, String>() {
                    //默认的数据加载实现,当调用get取值的时候,如果key没有对应的值,就调用这个方法进行加载.
                    @Override
                    public String load(String s) throws Exception {
                        return "null";
                    }
                });
    
        public static void setKey(String key,String value){
            localCache.put(key,value);
        }
    
        public static String getKey(String key){
            String value = null;
            try {
                value = localCache.get(key);
                if("null".equals(value)){
                    return null;
                }
                return value;
            }catch (Exception e){
                logger.error("localCache get error",e);
            }
            return null;
        }
    }
    
    
    
    

    UserServiceImpl.java

    public ServerResponse<String> checkAnswer(String username,String question,String answer){
            int resultCount = userMapper.checkAnswer(username,question,answer);
            if(resultCount>0){
                //说明问题及问题答案是这个用户的,并且是正确的
                String forgetToken = UUID.randomUUID().toString();
                TokenCache.setKey(TokenCache.TOKEN_PREFIX+username,forgetToken);
                return ServerResponse.createBySuccess(forgetToken);
            }
            return ServerResponse.createByErrorMessage("问题的答案错误");
        }
    
    
    
        public ServerResponse<String> forgetResetPassword(String username,String passwordNew,String forgetToken){
            if(org.apache.commons.lang3.StringUtils.isBlank(forgetToken)){
                return ServerResponse.createByErrorMessage("参数错误,token需要传递");
            }
            ServerResponse validResponse = this.checkValid(username,Const.USERNAME);
            if(validResponse.isSuccess()){
                //用户不存在
                return ServerResponse.createByErrorMessage("用户不存在");
            }
            String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX+username);
            if(org.apache.commons.lang3.StringUtils.isBlank(token)){
                return ServerResponse.createByErrorMessage("token无效或者过期");
            }
    
            if(org.apache.commons.lang3.StringUtils.equals(forgetToken,token)){
                String md5Password  = MD5Util.MD5EncodeUtf8(passwordNew);
                int rowCount = userMapper.updatePasswordByUsername(username,md5Password);
    
                if(rowCount > 0){
                    return ServerResponse.createBySuccessMessage("修改密码成功");
                }
            }else{
                return ServerResponse.createByErrorMessage("token错误,请重新获取重置密码的token");
            }
            return ServerResponse.createByErrorMessage("修改密码失败");
        }
    
    
    

    高复用服务响应对象的设计思想和封装

    ServerResponse.java

    package com.mmall.common;
    
    import org.codehaus.jackson.annotate.JsonIgnore;
    import org.codehaus.jackson.map.annotate.JsonSerialize;
    
    import java.io.Serializable;
    
    /**
     * Created by geely
     */
    @JsonSerialize(include =  JsonSerialize.Inclusion.NON_NULL)
    //保证序列化json的时候,如果是null的对象,key也会消失
    public class ServerResponse<T> implements Serializable {
    
        private int status;
        private String msg;
        private T data;
    
        private ServerResponse(int status){
            this.status = status;
        }
        private ServerResponse(int status,T data){
            this.status = status;
            this.data = data;
        }
    
        private ServerResponse(int status,String msg,T data){
            this.status = status;
            this.msg = msg;
            this.data = data;
        }
    
        private ServerResponse(int status,String msg){
            this.status = status;
            this.msg = msg;
        }
    
        @JsonIgnore
        //使之不在json序列化结果当中
        public boolean isSuccess(){
            return this.status == ResponseCode.SUCCESS.getCode();
        }
    
        public int getStatus(){
            return status;
        }
        public T getData(){
            return data;
        }
        public String getMsg(){
            return msg;
        }
    
    
        public static <T> ServerResponse<T> createBySuccess(){
            return new ServerResponse<T>(ResponseCode.SUCCESS.getCode());
        }
    
        public static <T> ServerResponse<T> createBySuccessMessage(String msg){
            return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(),msg);
        }
    
        public static <T> ServerResponse<T> createBySuccess(T data){
            return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(),data);
        }
    
        public static <T> ServerResponse<T> createBySuccess(String msg,T data){
            return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(),msg,data);
        }
    
    
        public static <T> ServerResponse<T> createByError(){
            return new ServerResponse<T>(ResponseCode.ERROR.getCode(),ResponseCode.ERROR.getDesc());
        }
    
    
        public static <T> ServerResponse<T> createByErrorMessage(String errorMessage){
            return new ServerResponse<T>(ResponseCode.ERROR.getCode(),errorMessage);
        }
    
        public static <T> ServerResponse<T> createByErrorCodeMessage(int errorCode,String errorMessage){
            return new ServerResponse<T>(errorCode,errorMessage);
        }
    
    }
    
    
    

    ResponseCode.java

    package com.mmall.common;
    
    /**
     * Created by geely
     */
    public enum ResponseCode {
    
        SUCCESS(0,"SUCCESS"),
        ERROR(1,"ERROR"),
        NEED_LOGIN(10,"NEED_LOGIN"),
        ILLEGAL_ARGUMENT(2,"ILLEGAL_ARGUMENT");
    
        private final int code;
        private final String desc;
    
    
        ResponseCode(int code,String desc){
            this.code = code;
            this.desc = desc;
        }
    
        public int getCode(){
            return code;
        }
        public String getDesc(){
            return desc;
        }
    
    }
    
    
    

    分类模块

    递归算法

        /**
         * 递归查询本节点的id及孩子节点的id
         * @param categoryId
         * @return
         */
        public ServerResponse<List<Integer>> selectCategoryAndChildrenById(Integer categoryId){
            Set<Category> categorySet = Sets.newHashSet();
            findChildCategory(categorySet,categoryId);
    
    
            List<Integer> categoryIdList = Lists.newArrayList();
            if(categoryId != null){
                for(Category categoryItem : categorySet){
                    categoryIdList.add(categoryItem.getId());
                }
            }
            return ServerResponse.createBySuccess(categoryIdList);
        }
    
    
        //递归算法,算出子节点
        private Set<Category> findChildCategory(Set<Category> categorySet ,Integer categoryId){
            Category category = categoryMapper.selectByPrimaryKey(categoryId);
            if(category != null){
                categorySet.add(category);
            }
            //查找子节点,递归算法一定要有一个退出的条件
            List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId);
            for(Category categoryItem : categoryList){
                findChildCategory(categorySet,categoryItem.getId());
            }
            return categorySet;
        }
    
    
    

    复杂对象排重

    Set集合

    重写自定义对象Category的equalshashCode方法

    无限层级树结构设计

    商品模块

    POJO、BO、VO抽象模型

    Product.java

    package com.mmall.pojo;
    
    import java.math.BigDecimal;
    import java.util.Date;
    
    public class Product {
        private Integer id;
    
        private Integer categoryId;
    
        private String name;
    
        private String subtitle;
    
        private String mainImage;
    
        private String subImages;
    
        private String detail;
    
        private BigDecimal price;
    
        private Integer stock;
    
        private Integer status;
    
        private Date createTime;
    
        private Date updateTime;
    
        public Product(Integer id, Integer categoryId, String name, String subtitle, String mainImage, String subImages, String detail, BigDecimal price, Integer stock, Integer status, Date createTime, Date updateTime) {
            this.id = id;
            this.categoryId = categoryId;
            this.name = name;
            this.subtitle = subtitle;
            this.mainImage = mainImage;
            this.subImages = subImages;
            this.detail = detail;
            this.price = price;
            this.stock = stock;
            this.status = status;
            this.createTime = createTime;
            this.updateTime = updateTime;
        }
    
        public Product() {
            super();
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public Integer getCategoryId() {
            return categoryId;
        }
    
        public void setCategoryId(Integer categoryId) {
            this.categoryId = categoryId;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name == null ? null : name.trim();
        }
    
        public String getSubtitle() {
            return subtitle;
        }
    
        public void setSubtitle(String subtitle) {
            this.subtitle = subtitle == null ? null : subtitle.trim();
        }
    
        public String getMainImage() {
            return mainImage;
        }
    
        public void setMainImage(String mainImage) {
            this.mainImage = mainImage == null ? null : mainImage.trim();
        }
    
        public String getSubImages() {
            return subImages;
        }
    
        public void setSubImages(String subImages) {
            this.subImages = subImages == null ? null : subImages.trim();
        }
    
        public String getDetail() {
            return detail;
        }
    
        public void setDetail(String detail) {
            this.detail = detail == null ? null : detail.trim();
        }
    
        public BigDecimal getPrice() {
            return price;
        }
    
        public void setPrice(BigDecimal price) {
            this.price = price;
        }
    
        public Integer getStock() {
            return stock;
        }
    
        public void setStock(Integer stock) {
            this.stock = stock;
        }
    
        public Integer getStatus() {
            return status;
        }
    
        public void setStatus(Integer status) {
            this.status = status;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        public Date getUpdateTime() {
            return updateTime;
        }
    
        public void setUpdateTime(Date updateTime) {
            this.updateTime = updateTime;
        }
    }
    
    
    

    ProductDetailVo.java

    package com.mmall.vo;
    
    import java.math.BigDecimal;
    
    /**
     * Created by geely
     */
    public class ProductDetailVo {
    
        private Integer  id;
        private Integer categoryId;
        private String name;
        private String subtitle;
        private String mainImage;
        private String subImages;
        private String detail;
        private BigDecimal price;
        private Integer stock;
        private Integer status;
        private String createTime;
        private String updateTime;
    
    
        private String imageHost;
        private Integer parentCategoryId;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public Integer getCategoryId() {
            return categoryId;
        }
    
        public void setCategoryId(Integer categoryId) {
            this.categoryId = categoryId;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getSubtitle() {
            return subtitle;
        }
    
        public void setSubtitle(String subtitle) {
            this.subtitle = subtitle;
        }
    
        public String getMainImage() {
            return mainImage;
        }
    
        public void setMainImage(String mainImage) {
            this.mainImage = mainImage;
        }
    
        public String getSubImages() {
            return subImages;
        }
    
        public void setSubImages(String subImages) {
            this.subImages = subImages;
        }
    
        public String getDetail() {
            return detail;
        }
    
        public void setDetail(String detail) {
            this.detail = detail;
        }
    
        public BigDecimal getPrice() {
            return price;
        }
    
        public void setPrice(BigDecimal price) {
            this.price = price;
        }
    
        public Integer getStock() {
            return stock;
        }
    
        public void setStock(Integer stock) {
            this.stock = stock;
        }
    
        public Integer getStatus() {
            return status;
        }
    
        public void setStatus(Integer status) {
            this.status = status;
        }
    
        public String getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(String createTime) {
            this.createTime = createTime;
        }
    
        public String getUpdateTime() {
            return updateTime;
        }
    
        public void setUpdateTime(String updateTime) {
            this.updateTime = updateTime;
        }
    
        public String getImageHost() {
            return imageHost;
        }
    
        public void setImageHost(String imageHost) {
            this.imageHost = imageHost;
        }
    
        public Integer getParentCategoryId() {
            return parentCategoryId;
        }
    
        public void setParentCategoryId(Integer parentCategoryId) {
            this.parentCategoryId = parentCategoryId;
        }
    }
    
    
    

    静态代码块>普通代码块>构造代码块

    Tomcat启动加载静态代码块

    时间转换工具类DateTimeUtil.java

    package com.mmall.util;
    
    import org.apache.commons.lang3.StringUtils;
    import org.joda.time.DateTime;
    import org.joda.time.format.DateTimeFormat;
    import org.joda.time.format.DateTimeFormatter;
    
    import java.util.Date;
    
    /**
     * Created by geely
     */
    public class DateTimeUtil {
    
        //joda-time
    
        //str->Date
        //Date->str
        public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
    
    
    
        public static Date strToDate(String dateTimeStr,String formatStr){
            DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);
            DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
            return dateTime.toDate();
        }
    
        public static String dateToStr(Date date,String formatStr){
            if(date == null){
                return StringUtils.EMPTY;
            }
            DateTime dateTime = new DateTime(date);
            return dateTime.toString(formatStr);
        }
    
        public static Date strToDate(String dateTimeStr){
            DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
            DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
            return dateTime.toDate();
        }
    
        public static String dateToStr(Date date){
            if(date == null){
                return StringUtils.EMPTY;
            }
            DateTime dateTime = new DateTime(date);
            return dateTime.toString(STANDARD_FORMAT);
        }
    
    
    
    
        public static void main(String[] args) {
            System.out.println(DateTimeUtil.dateToStr(new Date(),"yyyy-MM-dd HH:mm:ss"));
            System.out.println(DateTimeUtil.strToDate("2010-01-01 11:11:11","yyyy-MM-dd HH:mm:ss"));
    
        }
    
    
    }
    
    
    
    

    mybatis-pagehelper高效分页

    配置pom.xml

    实现

    mybatis-pagehelper动态排序

    mybatis对List遍历的实现方法

    mybatis对where语句动态拼装

    FTP服务对接

    先将文件上传到本地==>上传到远程ftp>删除本地文件

    配置pom.xml

      <!-- file upload -->
        <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
        <dependency>
          <groupId>commons-fileupload</groupId>
          <artifactId>commons-fileupload</artifactId>
          <version>1.2.2</version>
        </dependency>
    
        <dependency>
          <groupId>commons-io</groupId>
          <artifactId>commons-io</artifactId>
          <version>2.0.1</version>
        </dependency>
    
    
    

    配置dispatcher-servlet.xml

    <!-- 文件上传 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="10485760"/> <!-- 10m -->
        <property name="maxInMemorySize" value="4096" />
        <property name="defaultEncoding" value="UTF-8"></property>
    </bean>
    
    

    FileServiceImpl.java

    package com.mmall.service.impl;
    
    import com.google.common.collect.Lists;
    import com.mmall.service.IFileService;
    import com.mmall.util.FTPUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.UUID;
    
    /**
     * Created by geely
     */
    @Service("iFileService")
    public class FileServiceImpl implements IFileService {
    
        private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);
    
    
        public String upload(MultipartFile file,String path){
            String fileName = file.getOriginalFilename();
            //扩展名
            //abc.jpg
            String fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1);
            String uploadFileName = UUID.randomUUID().toString()+"."+fileExtensionName;
            logger.info("开始上传文件,上传文件的文件名:{},上传的路径:{},新文件名:{}",fileName,path,uploadFileName);
    
            File fileDir = new File(path);
            if(!fileDir.exists()){
                fileDir.setWritable(true);
                fileDir.mkdirs();
            }
            File targetFile = new File(path,uploadFileName);
    
    
            try {
                file.transferTo(targetFile);
                //文件已经上传成功了
    
    
                FTPUtil.uploadFile(Lists.newArrayList(targetFile));
                //已经上传到ftp服务器上
    
                targetFile.delete();
            } catch (IOException e) {
                logger.error("上传文件异常",e);
                return null;
            }
            //A:abc.jpg
            //B:abc.jpg
            return targetFile.getName();
        }
    
    }
    
    
    
    

    连接远程ftp,上传文件

    FTPUtil.java

    package com.mmall.util;
    
    import org.apache.commons.net.ftp.FTPClient;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.util.List;
    
    /**
     * Created by geely
     */
    public class FTPUtil {
    
        private static  final Logger logger = LoggerFactory.getLogger(FTPUtil.class);
    
        private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip");
        private static String ftpUser = PropertiesUtil.getProperty("ftp.user");
        private static String ftpPass = PropertiesUtil.getProperty("ftp.pass");
    
        public FTPUtil(String ip,int port,String user,String pwd){
            this.ip = ip;
            this.port = port;
            this.user = user;
            this.pwd = pwd;
        }
        public static boolean uploadFile(List<File> fileList) throws IOException {
            FTPUtil ftpUtil = new FTPUtil(ftpIp,21,ftpUser,ftpPass);
            logger.info("开始连接ftp服务器");
            boolean result = ftpUtil.uploadFile("img",fileList);
            logger.info("开始连接ftp服务器,结束上传,上传结果:{}");
            return result;
        }
    
    
        private boolean uploadFile(String remotePath,List<File> fileList) throws IOException {
            boolean uploaded = true;
            FileInputStream fis = null;
            //连接FTP服务器
            if(connectServer(this.ip,this.port,this.user,this.pwd)){
                try {
                    ftpClient.changeWorkingDirectory(remotePath);
                    ftpClient.setBufferSize(1024);
                    ftpClient.setControlEncoding("UTF-8");
                    ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                    ftpClient.enterLocalPassiveMode();
                    for(File fileItem : fileList){
                        fis = new FileInputStream(fileItem);
                        ftpClient.storeFile(fileItem.getName(),fis);
                    }
    
                } catch (IOException e) {
                    logger.error("上传文件异常",e);
                    uploaded = false;
                    e.printStackTrace();
                } finally {
                    fis.close();
                    ftpClient.disconnect();
                }
            }
            return uploaded;
        }
    
    
    
        private boolean connectServer(String ip,int port,String user,String pwd){
    
            boolean isSuccess = false;
            ftpClient = new FTPClient();
            try {
                ftpClient.connect(ip);
                isSuccess = ftpClient.login(user,pwd);
            } catch (IOException e) {
                logger.error("连接FTP服务器异常",e);
            }
            return isSuccess;
        }
    
    
        private String ip;
        private int port;
        private String user;
        private String pwd;
        private FTPClient ftpClient;
    
        public String getIp() {
            return ip;
        }
    
        public void setIp(String ip) {
            this.ip = ip;
        }
    
        public int getPort() {
            return port;
        }
    
        public void setPort(int port) {
            this.port = port;
        }
    
        public String getUser() {
            return user;
        }
    
        public void setUser(String user) {
            this.user = user;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    
        public FTPClient getFtpClient() {
            return ftpClient;
        }
    
        public void setFtpClient(FTPClient ftpClient) {
            this.ftpClient = ftpClient;
        }
    }
    
    
    
    

    富文本上传

    和ftp文件上传类似,只是对返回值有特殊要求

    购物车模块

    商品总价计算复用封装

    package com.mmall.util;
    
    import java.math.BigDecimal;
    
    /**
     * Created by geely
     */
    public class BigDecimalUtil {
    
        private BigDecimalUtil(){
    
        }
    
    
        public static BigDecimal add(double v1,double v2){
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.add(b2);
        }
    
        public static BigDecimal sub(double v1,double v2){
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.subtract(b2);
        }
    
    
        public static BigDecimal mul(double v1,double v2){
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.multiply(b2);
        }
    
        public static BigDecimal div(double v1,double v2){
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP);//四舍五入,保留2位小数
    
            //除不尽的情况
        }
    
    }
    
    
    
    

    高复用的逻辑方法封装思想

    解决商业运算丢失精度的坑

    一定要用BigDecimalString构造函数

    public class BigDecimalTest {
    
        @Test
        public void test1(){
            System.out.println(0.05+0.01);
            System.out.println(1.0-0.42);
            System.out.println(4.015*100);
            System.out.println(123.3/100);
        }
    
    
        @Test
        public void test2(){
            BigDecimal b1 = new BigDecimal(0.05);
            BigDecimal b2 = new BigDecimal(0.01);
            System.out.println(b1.add(b2));
        }
    
        @Test
        public void test3(){
            BigDecimal b1 = new BigDecimal("0.05");
            BigDecimal b2 = new BigDecimal("0.01");
            System.out.println(b1.add(b2));
    
        }
    
    }
    
    
    

    订单模块

    安全漏洞解决方案

    订单号生成规则

    强大的常量、枚举设计

    Const.java

    package com.mmall.common;
    
    import com.google.common.collect.Sets;
    
    import java.util.Set;
    
    /**
     * Created by geely
     */
    public class Const {
    
        public static final String CURRENT_USER = "currentUser";
    
        public static final String EMAIL = "email";
        public static final String USERNAME = "username";
    
        public interface ProductListOrderBy{
            Set<String> PRICE_ASC_DESC = Sets.newHashSet("price_desc","price_asc");
        }
    
        public interface Cart{
            int CHECKED = 1;//即购物车选中状态
            int UN_CHECKED = 0;//购物车中未选中状态
    
            String LIMIT_NUM_FAIL = "LIMIT_NUM_FAIL";
            String LIMIT_NUM_SUCCESS = "LIMIT_NUM_SUCCESS";
        }
    
        public interface Role{
            int ROLE_CUSTOMER = 0; //普通用户
            int ROLE_ADMIN = 1;//管理员
        }
    
        public enum ProductStatusEnum{
            ON_SALE(1,"在线");
            private String value;
            private int code;
            ProductStatusEnum(int code,String value){
                this.code = code;
                this.value = value;
            }
    
            public String getValue() {
                return value;
            }
    
            public int getCode() {
                return code;
            }
        }
    
    
        public enum OrderStatusEnum{
            CANCELED(0,"已取消"),
            NO_PAY(10,"未支付"),
            PAID(20,"已付款"),
            SHIPPED(40,"已发货"),
            ORDER_SUCCESS(50,"订单完成"),
            ORDER_CLOSE(60,"订单关闭");
    
    
            OrderStatusEnum(int code,String value){
                this.code = code;
                this.value = value;
            }
            private String value;
            private int code;
    
            public String getValue() {
                return value;
            }
    
            public int getCode() {
                return code;
            }
    
            public static OrderStatusEnum codeOf(int code){
                for(OrderStatusEnum orderStatusEnum : values()){
                    if(orderStatusEnum.getCode() == code){
                        return orderStatusEnum;
                    }
                }
                throw new RuntimeException("么有找到对应的枚举");
            }
        }
        public interface  AlipayCallback{
            String TRADE_STATUS_WAIT_BUYER_PAY = "WAIT_BUYER_PAY";
            String TRADE_STATUS_TRADE_SUCCESS = "TRADE_SUCCESS";
    
            String RESPONSE_SUCCESS = "success";
            String RESPONSE_FAILED = "failed";
        }
    
    
    
        public enum PayPlatformEnum{
            ALIPAY(1,"支付宝");
    
            PayPlatformEnum(int code,String value){
                this.code = code;
                this.value = value;
            }
            private String value;
            private int code;
    
            public String getValue() {
                return value;
            }
    
            public int getCode() {
                return code;
            }
        }
    
        public enum PaymentTypeEnum{
            ONLINE_PAY(1,"在线支付");
    
            PaymentTypeEnum(int code,String value){
                this.code = code;
                this.value = value;
            }
            private String value;
            private int code;
    
            public String getValue() {
                return value;
            }
    
            public int getCode() {
                return code;
            }
    
    
            public static PaymentTypeEnum codeOf(int code){
                for(PaymentTypeEnum paymentTypeEnum : values()){
                    if(paymentTypeEnum.getCode() == code){
                        return paymentTypeEnum;
                    }
                }
                throw new RuntimeException("么有找到对应的枚举");
            }
    
        }
    
    }
    
    
    

    使用


    mybatis批量插入

    收货地址

    同步获取自增主键

    数据绑定的对象绑定

    收货地址对象必须要有getset方法

    越权问题升级巩固

    支付模块

    支付宝SDK源码解析

    当面付

    当面付Demo

    服务端SDK下载和使用教程

    沙箱环境使用

    生成RSA密钥

    支付宝支付流程与集成

    导入依赖

    配置maven插件,除了pom.xml配置的依赖,lib包中的依赖也会一起打包部署

    配置沙箱环境zfbinfo.properties

    # 支付宝网关名、partnerId和appId
    open_api_domain = https://openapi.alipaydev.com/gateway.do
    mcloud_api_domain = http://mcloudmonitor.com/gateway.do
    pid = 2088102180444972
    appid = 2016102100728313
    
    
    # RSA私钥、公钥和支付宝公钥
    private_key = MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCKQ2pEIzXM4IoN1cMzOFXdlk8yVX2cKXWITZ92EGAQQcRytaV07yQOaz3UE9KTeT9Nu628G+HZMsJUxQjEUETagmY5nLtbeL35M2UcibYpM3e2gVTtUW86CA65GCdLzUhdIug8yf2F9zWayzG4sHZ9DcTezG6ZjFu+EtDpFgg+CtqY7n/ihjTIqeE1lX0C2ZIKpIYs7QjR8AztB/qRcpOJKRfMKGDgmT9GALN8LeFEYCbQ+W/GJHN8bQ0Bk1Ll6EKQ4cHXZ1Yko+aXaRfbXfUZYgD9hwAVlxtwZndgeFX8KapOCw0J25pzV4WkutIjMlt7I2Q1jaWNoKLuxtz4M2mzAgMBAAECggEAAhnsL4TpeGehMXyiLtEYXPm/0mACPLFUm/GyDrVJAHY/ag7gqNpJjf6LPgHfHWamU6Qai9VQpWBkG62y6Gjf4wJAU3fSUR2QpYzmaHyfTBkAJMHqbIDkU9lzf9SiJEDGbMPvC512QOb05ZlY9Bmac2QWLdylgafkbQsUKbawAWFa/BAOMIp0tgYLW8/yY2aG6jeLqhOgTo8MWIW5d1qHtX5m/x7g97dYYMdX3kTo2i1dFLUVfEOvZe4US6VBvLg71dMxwadVF5YMaY9jq/ShPD0Gkf29wdThwsjcH6u9Tq/KArQTK+z02DAGkdWOcue3pHql+gvoIA8U5uFDdIeYwQKBgQDri3jPkDKi48efdKQk38rn+CJYeNFNRAhlly3h2AHaFEY92XRlBsho/vGFg43BvHu+cMz0Run4SS8Vo09vcTIY6p2xNMffjR0w2gQqx6PUdGHBFtw7FavxN4uVtVhL6uTAqfBv97mqQO0bq+DhOGwSRNIWqvnzfXywqwmXhKYECwKBgQCWRTl6tNv8scxPq4fpRL/uw71TU6XqSS/nME7KT4uyQPAXPk0mXVVwdmyST9Crlr6O6WJopPe9nMIFUYdjdkLfGKLCR96AH3U7frr4jf60eDYEhfHGIzln/ptrTJLvvbXTaPctAaZd6TIv63QVz3yim4MMl3VSdRlrE+O9R5ZR+QKBgQDjEP8TyUSnNsJX+4/JZFwsp04kz8OlorIdjVHT5/JREz5rnVfRlGpanXqjZSCg5Vy9R+ysiDhA+/wB9f87xXmv/2ypSeJspZLAZ0uhGffbdZ5PEASaiNfKn+tWFQ3bkcOX37tDlSJM+G4bQOR2+XdlXSbSZ1yx2AT+IsQKZvvL5QKBgQCPZEUiEz0sV1kX2R2a+XCQ3RVnUxWqh+X/HPjCUr+B/DdeZqPl7QAfjdGymBkN842o/4lZQ7nnpJL70j14KpxLGM4Ox9fIuLv8ZsTxc0XOXjtle48nO+uGkc0qyWoY/RVpQ+tBdiaTzHeIhIxEV7adz/lwZYKdiYIUzGjv8ES/uQKBgCgeWysXjahCQItxx5fTrS8SQFP7Dx5vDW+UkqQ2pbL0AlHyUS7pWJj3AAe3pn4AJZZp4SZPoQP+Z8JPqDA6MrQWHYMi0XkMuMYwLWbGCkmf1MnjUxgOaLXoItjxS/y3jQfeOmHhmOAVkjnEvAh+BWlZxFMv2kiuHRU72bNa0rDI
    
    public_key = MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAikNqRCM1zOCKDdXDMzhV3ZZPMlV9nCl1iE2fdhBgEEHEcrWldO8kDms91BPSk3k/TbutvBvh2TLCVMUIxFBE2oJmOZy7W3i9+TNlHIm2KTN3toFU7VFvOggOuRgnS81IXSLoPMn9hfc1mssxuLB2fQ3E3sxumYxbvhLQ6RYIPgramO5/4oY0yKnhNZV9AtmSCqSGLO0I0fAM7Qf6kXKTiSkXzChg4Jk/RgCzfC3hRGAm0PlvxiRzfG0NAZNS5ehCkOHB12dWJKPml2kX2131GWIA/YcAFZcbcGZ3YHhV/CmqTgsNCduac1eFpLrSIzJbeyNkNY2ljaCi7sbc+DNpswIDAQAB
    
    
    
    #SHA1withRsa对应支付宝公钥
    #alipay_public_key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkrIvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsraprwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUrCmZYI/FCEa3/cNMW0QIDAQAB
    
    #SHA256withRsa对应支付宝公钥
    alipay_public_key = MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqzWgVL/NWrJAeyEImwtaK3IDwj0dKkqUDIfqqWn5SiLaWMYi9RmKhn+jY9VM7JXEIkYYeVlqIL6Xn7OvYFRTi4buTCXGKvFLn95aDcaur77/S/0ibcdN1K2wIoHzaqQhXAb1ezKxTnFP7OLJsAL22b0NzrQDj2OH9SA06gJb8nHBfR+7Sx7DfwcqE0OtTcDHjbbcB24Qgg/dfItxoEnKuSyRVrf6BtpUnJxSzG/Ge7FfF+VBq8re1t4ZTSxaDEjto071I5VFBxr7I4SyqZsc7WpAmZL8AqUgEbQ1XYBWx2LnpJXM5GQW/thUvcDDqzea7LJNWJOQPM5DaZQgu7QuuwIDAQAB
    
    
    # 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa
    sign_type = RSA2
    
    # 当面付最大查询次数和查询间隔(毫秒)
    max_query_retry = 5
    query_duration = 5000
    
    # 当面付最大撤销次数和撤销间隔(毫秒)
    max_cancel_retry = 3
    cancel_duration = 2000
    
    # 交易保障线程第一次调度延迟和调度间隔(秒)
    heartbeat_delay = 5
    heartbeat_duration = 900
    
    
    

    二维码生成,扫码支付

    配置回调url

    两次回调,扫码进行一次回调,扫码付款成功进行一次回调

    生成二维码,上传到ftp服务器

    OrderServiceImpl.java

    package com.mmall.service.impl;
    
    /**
     * Created by geely
     */
    @Service("iOrderService")
    public class OrderServiceImpl implements IOrderService {
    
    
        private static  AlipayTradeService tradeService;
        static {
    
            /** 一定要在创建AlipayTradeService之前调用Configs.init()设置默认参数
             *  Configs会读取classpath下的zfbinfo.properties文件配置信息,如果找不到该文件则确认该文件是否在classpath目录
             */
            Configs.init("zfbinfo.properties");
    
            /** 使用Configs提供的默认参数
             *  AlipayTradeService可以使用单例或者为静态成员对象,不需要反复new
             */
            tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();
        }
    
        private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
    
        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private OrderItemMapper orderItemMapper;
        @Autowired
        private PayInfoMapper payInfoMapper;
        @Autowired
        private CartMapper cartMapper;
        @Autowired
        private ProductMapper productMapper;
        @Autowired
        private ShippingMapper shippingMapper;
    
    
    
        public ServerResponse pay(Long orderNo,Integer userId,String path){
            Map<String ,String> resultMap = Maps.newHashMap();
            Order order = orderMapper.selectByUserIdAndOrderNo(userId,orderNo);
            if(order == null){
                return ServerResponse.createByErrorMessage("用户没有该订单");
            }
            resultMap.put("orderNo",String.valueOf(order.getOrderNo()));
    
    
    
            // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
            // 需保证商户系统端不能重复,建议通过数据库sequence生成,
            String outTradeNo = order.getOrderNo().toString();
    
    
            // (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
            String subject = new StringBuilder().append("happymmall扫码支付,订单号:").append(outTradeNo).toString();
    
    
            // (必填) 订单总金额,单位为元,不能超过1亿元
            // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
            String totalAmount = order.getPayment().toString();
    
    
            // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
            // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
            String undiscountableAmount = "0";
    
    
    
            // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
            // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
            String sellerId = "";
    
            // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
            String body = new StringBuilder().append("订单").append(outTradeNo).append("购买商品共").append(totalAmount).append("元").toString();
    
    
            // 商户操作员编号,添加此参数可以为商户操作员做销售统计
            String operatorId = "test_operator_id";
    
            // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
            String storeId = "test_store_id";
    
            // 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
            ExtendParams extendParams = new ExtendParams();
            extendParams.setSysServiceProviderId("2088100200300400500");
    
    
    
    
            // 支付超时,定义为120分钟
            String timeoutExpress = "120m";
    
            // 商品明细列表,需填写购买商品详细信息,
            List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>();
    
            List<OrderItem> orderItemList = orderItemMapper.getByOrderNoUserId(orderNo,userId);
            for(OrderItem orderItem : orderItemList){
                GoodsDetail goods = GoodsDetail.newInstance(orderItem.getProductId().toString(), orderItem.getProductName(),
                        BigDecimalUtil.mul(orderItem.getCurrentUnitPrice().doubleValue(),new Double(100).doubleValue()).longValue(),
                        orderItem.getQuantity());
                goodsDetailList.add(goods);
            }
    
            // 创建扫码支付请求builder,设置请求参数
            AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
                    .setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
                    .setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
                    .setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams)
                    .setTimeoutExpress(timeoutExpress)
                    .setNotifyUrl(PropertiesUtil.getProperty("alipay.callback.url"))//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
                    .setGoodsDetailList(goodsDetailList);
    
    
            AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
            switch (result.getTradeStatus()) {
                case SUCCESS:
                    logger.info("支付宝预下单成功: )");
    
                    AlipayTradePrecreateResponse response = result.getResponse();
                    dumpResponse(response);
    
                    File folder = new File(path);
                    if(!folder.exists()){
                        folder.setWritable(true);
                        folder.mkdirs();
                    }
    
                    // 需要修改为运行机器上的路径
                    //细节细节细节
                    String qrPath = String.format(path+"/qr-%s.png",response.getOutTradeNo());
                    String qrFileName = String.format("qr-%s.png",response.getOutTradeNo());
                    ZxingUtils.getQRCodeImge(response.getQrCode(), 256, qrPath);
    
                    File targetFile = new File(path,qrFileName);
                    try {
                        FTPUtil.uploadFile(Lists.newArrayList(targetFile));
                    } catch (IOException e) {
                        logger.error("上传二维码异常",e);
                    }
                    logger.info("qrPath:" + qrPath);
                    String qrUrl = PropertiesUtil.getProperty("ftp.server.http.prefix")+targetFile.getName();
                    resultMap.put("qrUrl",qrUrl);
                    return ServerResponse.createBySuccess(resultMap);
                case FAILED:
                    logger.error("支付宝预下单失败!!!");
                    return ServerResponse.createByErrorMessage("支付宝预下单失败!!!");
    
                case UNKNOWN:
                    logger.error("系统异常,预下单状态未知!!!");
                    return ServerResponse.createByErrorMessage("系统异常,预下单状态未知!!!");
    
                default:
                    logger.error("不支持的交易状态,交易返回异常!!!");
                    return ServerResponse.createByErrorMessage("不支持的交易状态,交易返回异常!!!");
            }
    
        }
    
        // 简单打印应答
        private void dumpResponse(AlipayResponse response) {
            if (response != null) {
                logger.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
                if (StringUtils.isNotEmpty(response.getSubCode())) {
                    logger.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
                            response.getSubMsg()));
                }
                logger.info("body:" + response.getBody());
            }
        }
    
    
        public ServerResponse aliCallback(Map<String,String> params){
            Long orderNo = Long.parseLong(params.get("out_trade_no"));
            String tradeNo = params.get("trade_no");
            String tradeStatus = params.get("trade_status");
            Order order = orderMapper.selectByOrderNo(orderNo);
            if(order == null){
                return ServerResponse.createByErrorMessage("非快乐慕商城的订单,回调忽略");
            }
            if(order.getStatus() >= Const.OrderStatusEnum.PAID.getCode()){
                return ServerResponse.createBySuccess("支付宝重复调用");
            }
            if(Const.AlipayCallback.TRADE_STATUS_TRADE_SUCCESS.equals(tradeStatus)){
                order.setPaymentTime(DateTimeUtil.strToDate(params.get("gmt_payment")));
                order.setStatus(Const.OrderStatusEnum.PAID.getCode());
                orderMapper.updateByPrimaryKeySelective(order);
            }
    
            PayInfo payInfo = new PayInfo();
            payInfo.setUserId(order.getUserId());
            payInfo.setOrderNo(order.getOrderNo());
            payInfo.setPayPlatform(Const.PayPlatformEnum.ALIPAY.getCode());
            payInfo.setPlatformNumber(tradeNo);
            payInfo.setPlatformStatus(tradeStatus);
    
            payInfoMapper.insert(payInfo);
    
            return ServerResponse.createBySuccess();
        }
    
    
        public ServerResponse queryOrderPayStatus(Integer userId,Long orderNo){
            Order order = orderMapper.selectByUserIdAndOrderNo(userId,orderNo);
            if(order == null){
                return ServerResponse.createByErrorMessage("用户没有该订单");
            }
            if(order.getStatus() >= Const.OrderStatusEnum.PAID.getCode()){
                return ServerResponse.createBySuccess();
            }
            return ServerResponse.createByError();
        }
    
    }
    
    
    
    
    

    内网穿透

    NATAPP1分钟快速新手图文教程

    线上部署

    云服务器vsftpd、nginx等配置

    云服务器的配置与域名解析

    发布上线注意事项

    本文由博客一文多发平台 OpenWrite 发布!

  • 相关阅读:
    C# 关于反射事件
    SqlBulkCopy 帮助类
    【转载】FormsAuthenticationTicket 对象
    c# 安全队列
    用户离职后网盘数据交接的实现
    网盘文件被下载过的记录查询脚本(亲测有效)
    学校搭建教学资源库平台的成熟方案
    【不务正业】太空工程师自动导航v1.0 beta
    golang的sort研究
    关于golang的defer的练习
  • 原文地址:https://www.cnblogs.com/lisingshen/p/11794495.html
Copyright © 2020-2023  润新知