一个基于Java Web的商城后台管理系统。模仿 Spring Boot 的核心特性,实现了自己的依赖注入容器和 MVC 框架。

06-01 1223阅读

1. 系统简介

本购物系统分为管理端和用户端两部分,旨在实现高效的商品管理与用户购物体验。管理端为管理员提供核心操作功能,包括用户管理、商品管理和订单管理。管理员可通过系统对用户信息进行查询、增删改查商品及其库存,并支持条件查询订单。

用户端面向公众和会员,功能丰富。公共模块支持商品分类浏览、商品详情和评价查看以及问题咨询。会员模块进一步提供登录注册、购物车管理、订单查询及评价功能。用户可通过平台轻松完成商品的购买流程,包括加入购物车、一键下单等操作。

整体系统设计注重用户体验,功能划分清晰,操作便捷,为商家和用户提供了高效、友好的购物服务。

项目仓库地址:https://gitee.com/enhead/shop

2. 系统功能模块图

一个基于Java Web的商城后台管理系统。模仿 Spring Boot 的核心特性,实现了自己的依赖注入容器和 MVC 框架。

3. 表设计

表1 管理员信息表(admins)

列名数据类型长度是否为空描述
idBIGINT管理员ID(主键)
accountVARCHAR255管理员账号(唯一)
nameVARCHAR64管理员姓名
pwdVARCHAR255管理员密码

表2 用户信息表(users)

列名数据类型长度是否为空描述
idBIGINT用户ID(主键)
emailVARCHAR64用户邮箱(唯一)
pwdVARCHAR255用户密码
nicknameVARCHAR64用户昵称(唯一)
sexINT性别(0-保密,1-男性,2-女性)
recipientVARCHAR64收件人姓名
addressVARCHAR500收货地址
phoneVARCHAR64联系电话
headimgVARCHAR500用户头像
updatetimeDATETIME更新时间
createtimeDATETIME创建时间

表3 商品类别信息表(types)

列名数据类型长度是否为空描述
idBIGINT类别ID(主键)
nameVARCHAR255类别名称

表4 商品信息表(goods)

列名数据类型长度是否为空描述
idBIGINT商品ID(主键)
nameVARCHAR500商品名称
typeIdBIGINT商品类别ID
imgVARCHAR500商品图片URL
descTEXT商品描述
updatetimeDATETIME更新时间
createtimeDATETIME创建时间

表5 商品规格信息表(goodsDetails)

列名数据类型长度是否为空描述
idBIGINT商品详情ID(主键)
goodsIdBIGINT商品ID
specNameVARCHAR500规格名称
stockNumINT库存数量
unitPriceFLOAT单价
updatetimeDATETIME更新时间
createtimeDATETIME创建时间

表6 商品评价信息表(comments)

列名数据类型长度是否为空描述
idBIGINT评价ID(主键)
userIdBIGINT评价用户ID
goodsIdBIGINT商品ID
goodsDetailIdBIGINT商品规格详情ID
orderIdBIGINT订单ID
contentVARCHAR500评价内容
scoreINT评分(1-5分)
createtimeDATETIME创建时间

表7 留言信息表(messages)

列名数据类型长度是否为空描述
idBIGINT留言ID(主键)
userIdBIGINT用户ID
goodsIdBIGINT商品ID
contentVARCHAR500留言内容
stateINT留言状态(0-未回复,1-已回复)
createtimeDATETIME创建时间

表8 订单信息表(orders)

列名数据类型长度是否为空描述
idBIGINT订单ID(主键)
userIdBIGINT用户ID
goodsDetailIdBIGINT商品规格详情ID
goodsNumINT商品数量
amountFLOAT订单金额
stateINT订单状态
updatetimeDATETIME更新时间
createtimeDATETIME创建时间

表9 留言回复信息表(replies)

列名数据类型长度是否为空描述
idBIGINT回复ID(主键)
messageIdBIGINT留言ID
contentVARCHAR500回复内容
createtimeDATETIME创建时间

实体关系图:(EDR图)

一个基于Java Web的商城后台管理系统。模仿 Spring Boot 的核心特性,实现了自己的依赖注入容器和 MVC 框架。

4. 系统实现

系统实现概述

本商城系统采用前后端分离架构,前端使用了 Vue.js 进行开发,后端基于 Servlet 技术实现,并通过反射机制简化实现了 SpringMVC 的基础注解功能。整个系统遵循 RESTful 规范,前后端通过 HTTP 协议进行交互,确保系统的简洁性与可扩展性。

在前端部分,采用 Vue.js 实现响应式的用户界面,配合 axios 实现与后端的 HTTP 请求交互。系统遵循 RESTful API 设计原则,前端通过 GET、POST 等 HTTP 请求方法与后端交互,操作商品、用户和订单等资源。数据格式主要采用 JSON,以保证数据传输的高效性与兼容性。

后端部分,通过传统的 Servlet 技术处理 HTTP 请求,并通过反射机制实现了简化版的 SpringMVC 控制层注解功能。通过自定义注解,如 @RestController、@RequestMapping和全局异常处理器 等,模拟了 SpringMVC 的请求映射和响应处理机制,使得系统能够更灵活地处理用户请求并返回相应的结果。同时,IOC 容器的实现通过反射扫描组件类并进行依赖注入,进一步简化了代码结构。

在团队合作方面,我们采用了 Git 进行版本控制,并通过 Gitee 进行代码托管和协作开发。API 文档的编写和管理使用了 ApiFox,确保了前后端开发的同步与接口的一致性。此外,团队成员之间通过不断的代码审查和讨论,确保了系统的高效开发和稳定性。

通过这些技术实现,本商城系统具备了高效、可扩展、易维护的特点,能够为用户提供流畅的购物体验。

4.1 技术选型

4.1.1 后端部分

本商城系统的后端基于Java开发,采用传统的 Servlet 和 JSP 技术进行搭建,注重简洁性和高效性,主要技术选型如下:

  • 开发工具与环境

    • Maven:作为项目的构建和依赖管理工具,用于管理依赖库、简化项目的编译与打包过程。

    • Java 11:提供现代化语言特性,如模块化系统和改进的垃圾回收机制,提升开发效率和性能。

    • Web 技术

      • Jakarta Servlet API:处理 HTTP 请求和响应,实现核心的服务端逻辑。

      • Jakarta JSP API:用于动态生成 HTML 页面,支持简单的数据展示和用户交互。

      • Commons BeanUtils:提供便捷的 JavaBean 操作能力,提高代码的可读性与可维护性。

      • 数据库与连接池

        • MySQL Connector:支持与 MySQL 数据库进行高效交互,提供稳定的数据库访问功能。

        • Druid:阿里巴巴开源的数据库连接池,具备高性能、稳定性和强大的监控功能。

        • Commons DBUtils:封装 JDBC 操作,简化数据库查询与事务处理。

        • 数据处理与安全

          • FastJSON:高性能的 JSON 序列化与反序列化工具,用于后端与前端的数据传输。

          • JJWT:用于生成和验证 JWT(JSON Web Token),支持安全的身份认证与授权。

          • JBCrypt:基于 BCrypt 算法的密码加密工具,用于存储用户密码,增强系统安全性。

          • 日志与调试

            • Log4j:高效、灵活的日志框架,用于记录系统运行状态,支持多种日志级别和输出方式。

            • Reflections:用于反射操作,便于动态加载类或处理运行时元数据。

              4.1.2 前端部分

              前端部分是有原型项目的,在此项目的项目做出一点小改动,项目地址:https://gitee.com/enhead/shop-prototype

              本系统的前端部分采用了基于 Vue.js 的技术栈进行开发,具体选型如下:

              • 核心框架

                使用 Vue.js 作为核心框架,提供了响应式的数据绑定与组件化开发模式,能够快速构建高性能的单页应用程序(SPA)。

              • 路由管理

                采用 vue-router 进行前端路由管理,支持多页面间的路由跳转与参数传递,实现了视图的动态切换。

              • 状态管理

                借助 Vuex 实现全局状态管理,用于管理购物车、用户信息等共享数据,保证了组件间数据的统一性和可维护性。

              • HTTP请求

                使用 axios 作为 HTTP 客户端,用于与后端 API 进行数据交互,支持请求拦截与响应处理,简化了异步请求的管理。

              • 模块打包

                使用 Webpack 作为打包工具,通过 vue-loader 和 babel 编译工具支持 Vue 单文件组件的开发及 ES6 语法的转译,提升了开发效率和代码兼容性。

              • 样式处理

                前端样式处理集成了 less 和 less-loader,支持灵活的样式预处理,提高了样式的可复用性和开发效率。

              • 辅助工具

                • html-webpack-plugin:生成自动引入依赖的 HTML 文件。
                • copy-webpack-plugin:用于静态资源的拷贝和处理。
                • friendly-errors-webpack-plugin:优化开发过程中的错误提示,提升开发体验。
                • 浏览器兼容性

                  配置了 autoprefixer 和 browserslist,自动添加浏览器前缀,确保兼容大多数主流浏览器。

                  通过上述技术选型,系统的前端具备了高性能、模块化、易维护和高兼容性的特点,能够满足商城系统的复杂业务需求。

                  4.1.3 团队开发部分

                  本商城系统的开发团队使用了现代化的协作工具和版本控制系统,以确保开发过程高效、协同。主要的团队合作开发工具选型如下:

                  • 版本控制与代码托管
                    • Git:作为分布式版本控制系统,用于管理代码的版本、分支和合并,确保团队成员能够高效协作,避免代码冲突。
                    • Gitee:作为代码托管平台,提供 Git 仓库管理、协作开发和版本控制服务。Gitee 支持快速的代码提交与代码审查流程,提升团队协作效率。
                    • 接口文档与测试
                      • ApiFox:用于接口文档的编写与管理,支持团队成员共同编写、更新 API 文档,确保接口的一致性与可用性。ApiFox 还具备接口调试功能,帮助开发人员快速测试接口,保证后端与前端的接口对接准确无误。

                        4.2 系统框架实现

                        4.2.1 基于servelet、序列化和放射机制实现SpringMVC基础注解
                        4.2.1.1 功能概述

                        这段代码的核心可以分为两个部分:IOC容器和AOP请求处理器。

                        1. IOC容器(依赖注入容器):
                          • 负责管理应用中的所有组件和服务实例。在本代码中,IOC容器通过 ApplicationContext 类提供,负责存储和获取控制器实例及其依赖对象。
                          • 通过注解扫描 (@RestController 和 @RestControllerAdvice),IOC 容器自动识别并实例化控制器和全局异常处理类,并进行管理。
                          • 容器提供依赖注入功能,确保控制器和异常处理器能通过反射机制正确地被调用。
                          • AOP请求处理器:
                            • 通过注解驱动的方式(如 @GetMapping, @PostMapping 等),AopRequestHandler 负责将 HTTP 请求映射到相应的控制器方法。
                            • 处理请求时,首先解析请求路径和请求方法,然后通过反射机制调用对应的控制器方法,并将请求参数(通过 @RequestParam 和 @RequestBody 注解)传递给方法。
                            • 对于异常,AOP 请求处理器会根据配置的全局异常处理器(通过 @ExceptionHandler 注解)捕获并处理,返回 JSON 格式的错误信息。
                            • 通过 response 对象统一设置响应格式(如 JSON 格式和跨域支持)。

                        简而言之,IOC容器负责管理应用中的组件实例,而AOP请求处理器通过注解配置和反射机制处理请求、参数解析和异常捕获等逻辑。

                        4.1.1.2 代码实现
                        package com.wawu.common.httpHandler;
                        import jakarta.servlet.ServletException;
                        import jakarta.servlet.annotation.WebServlet;
                        import jakarta.servlet.http.HttpServlet;
                        import jakarta.servlet.http.HttpServletRequest;
                        import jakarta.servlet.http.HttpServletResponse;
                        import java.io.IOException;
                        //TODO 待优化:请求路径处理冗余了
                        //  这里我规定/api,还会进行中心代理,返回json
                        @WebServlet("/api/*")
                        public class AopServlet extends HttpServlet {
                            @Override
                            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                                // 通过AopRequestHandler处理请求
                                AopRequestHandler.handleRequest(request, response);
                            }
                        }
                        
                        package com.wawu.common.httpHandler;
                        import com.alibaba.fastjson2.JSON;
                        import com.alibaba.fastjson2.JSONArray;
                        import com.alibaba.fastjson2.JSONObject;
                        import com.wawu.common.IOC.ApplicationContext;
                        import com.wawu.common.annotation.controller.globalExceptionHandle.ExceptionHandler;
                        import com.wawu.common.annotation.controller.mapping.*;
                        import com.wawu.common.annotation.controller.parameter.RequestBody;
                        import com.wawu.common.annotation.controller.parameter.RequestParam;
                        import com.wawu.common.enumeration.RequestMethodEnum;
                        import com.wawu.common.property.TomcatProperty;
                        import jakarta.servlet.http.HttpServletRequest;
                        import jakarta.servlet.http.HttpServletResponse;
                        import org.apache.commons.beanutils.ConvertUtils;
                        import java.io.IOException;
                        import java.lang.reflect.Method;
                        import java.lang.reflect.Parameter;
                        import java.lang.reflect.ParameterizedType;
                        import java.lang.reflect.Type;
                        import java.text.SimpleDateFormat;
                        import java.time.LocalDateTime;
                        import java.time.format.DateTimeFormatter;
                        import java.time.format.DateTimeParseException;
                        import java.util.Date;
                        import java.util.HashMap;
                        import java.util.List;
                        import java.util.Map;
                        /**
                         * 使用切面编程处理原生的Servlet
                         * 功能:
                         *  1. 路由注册以及控制层实例的映射
                         *  2. 分配实际的请求相应,并以json格式放回
                         *
                         * 包含注解(控制层相关注解的简化):
                         *  1. @RestController,@RequestMapping,@请求方式Mapping
                         *  2. 请求方法接受方法相关注解:@RequestBody,@RequestParam
                         */
                        public class AopRequestHandler {
                            private static final Map routes = new HashMap();//请求的路由映射
                            private static final Map restControllers = new HashMap(); // 路由对应控制器实例(来自IOC容器中的bean对象)
                            //TODO:待优化: 1.如果需要简化代码:①请求路径以及方法可以放在@RequestMapping,但是这里还需要完成注解(类似`@AliasFor(annotation = Component.class)`用来处理别名);②简单的办法可以定义一个集合来枚举
                            //             2.支持多路径、多请求方式,这里默认只有一个路径
                            //             3.当@请求方式Mapping为空时,特判
                            /**
                             * 初始化路由
                             *  - 注册路由
                             *  - 注册请求控制对应的实例
                             * @param context:IOC容器
                             */
                            public static void initializeRoutes(ApplicationContext context) {
                                //获取所有被@RestController修饰的类及其方法
                                for (Class restcontrollerClass : context.getRestControllerClazzes()) {
                                    Object restController = context.getBean(restcontrollerClass);
                                    String basePath = "";
                                        //类上的是父路径
                                    if (restcontrollerClass.isAnnotationPresent(RequestMapping.class)) {
                                        basePath = restcontrollerClass.getAnnotation(RequestMapping.class).value()[0];
                                    }
                                        //类方法上还有子路径
                                    //合成完成的路由同时进行注册
                                    for (Method method : restcontrollerClass.getDeclaredMethods()) {
                                        String methodPath = ""; // 方法的路径,默认空
                                        if (method.isAnnotationPresent(GetMapping.class)) {
                                            methodPath = method.getAnnotation(GetMapping.class).value()[0];
                                            registerRoute(basePath, methodPath, RequestMethodEnum.GET, method, restController);
                                        } else if (method.isAnnotationPresent(PostMapping.class)) {
                                            methodPath = method.getAnnotation(PostMapping.class).value()[0];
                                            registerRoute(basePath, methodPath, RequestMethodEnum.POST, method, restController);
                                        } else if (method.isAnnotationPresent(PutMapping.class)) {
                                            methodPath = method.getAnnotation(PutMapping.class).value()[0];
                                            registerRoute(basePath, methodPath, RequestMethodEnum.PUT, method, restController);
                                        } else if (method.isAnnotationPresent(DeleteMapping.class)) {
                                            methodPath = method.getAnnotation(DeleteMapping.class).value()[0];
                                            registerRoute(basePath, methodPath, RequestMethodEnum.DELETE, method, restController);
                                        }
                                    }
                                }
                                System.out.println("基础请求路由:"+routes);
                                System.out.println("请求控制层:"+restControllers);
                                System.out.println("注意:默认是去掉Tomcat对应的上下文"+ TomcatProperty.CONTEXT_PATH);
                            }
                            /**
                             * 注册请求路径和方法映射
                             * @param basePath:类上的父路径
                             * @param methodPath:子类上的子路径
                             * @param methodEnum:请求方式
                             * @param method:放射中的方法
                             * @param controller:后续将要进行调用的实体类
                             */
                            private static void registerRoute(String basePath, String methodPath, RequestMethodEnum methodEnum, Method method, Object controller) {
                                String fullPath = (basePath + methodPath).replaceAll("/+", "/");
                                routes.put(methodEnum.name() + fullPath, method);
                                restControllers.put(methodEnum.name() + fullPath, controller); // 记录控制器实例
                            }
                            /从这里开始写注册异常处理部分/
                            //大部分其实就是参照上面
                            private static final Map parameterType = parameters[0].getType();
                                                    if (Throwable.class.isAssignableFrom(parameterType)) {
                                                        exceptionTypes = new Class[]{(Class type) {
                                //之前有问题,这里如果是long或者是自定义类型咋办?
                        //        if (value == null) return null;
                        //        if (type == Integer.class || type == int.class) {
                        //            return Integer.parseInt(value);
                        //        } else if (type == Double.class || type == double.class) {
                        //            return Double.parseDouble(value);
                        //        }
                        //        return value;
                                if (value == null) return null;
                                try {
                                    // 使用 ConvertUtils 转换基本类型和常见类型
                                        //TODO 后续有时间添加时间类型
                                        //  这个部分赶时间由GPT生成了
                                    // 处理 LocalDateTime 类型
                                    if (type == LocalDateTime.class) {
                                        // 处理常见的日期格式,包括带时间和不带时间的日期
                                        DateTimeFormatter formatter;
                                        // 如果是带时间的格式 (例如 yyyy-MM-dd HH:mm:ss)
                                        if (value.contains("T")) {
                                            formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
                                        }
                                        // 如果是日期格式 (例如 yyyy-MM-dd),就加上默认时间
                                        else if (value.matches("\\d{4}-\\d{2}-\\d{2}")) {
                                            value += "T00:00:00"; // 加上默认时间
                                            formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
                                        } else {
                                            throw new DateTimeParseException("Invalid date time format", value, 0);
                                        }
                                        return LocalDateTime.parse(value, formatter);
                                    }
                                    // 处理 java.util.Date 类型
                                    if (type == Date.class) {
                                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                        return sdf.parse(value);
                                    }
                                    return ConvertUtils.convert(value, type);
                                } catch (Exception e) {
                                    System.out.println("Type conversion error for value: " + value + ", target type: " + type.getName());
                                    e.printStackTrace();
                                }
                                return value;
                            }
                        }
                        
                        package com.wawu.common.IOC;
                        import com.wawu.common.annotation.IOC.Autowired;
                        import com.wawu.common.annotation.IOC.component.Component;
                        import com.wawu.common.annotation.IOC.component.RestController;
                        import com.wawu.common.annotation.controller.globalExceptionHandle.RestControllerAdvice;
                        import com.wawu.common.utils.AnnotationUtils;
                        import lombok.Getter;
                        import org.reflections.Reflections;
                        import java.lang.annotation.Annotation;
                        import java.lang.reflect.Field;
                        import java.util.HashMap;
                        import java.util.HashSet;
                        import java.util.Map;
                        import java.util.Set;
                        import java.util.stream.Collectors;
                        /**
                         * 实现IOC容器(控制反转和依赖注入)
                         * 表现:使用@Component及其相关注解注册bean对象,然后使用@Autowired进行注入
                         */
                        public class ApplicationContext {
                            private Set, Object> beanMap = new HashMap();  // key:类;value:实例;(单例模式)
                                //这个类似工厂模式
                            @Getter
                            private Set> restControllerAdviceClazzes=new HashSet();//存放所有的全局异常处理器
                            public ApplicationContext(String basePackage,String baseAnnotationPackage) {
                                // 1. 获取所有自定义注解,并筛选出需要注册的注解(这里逻辑上可能还有问题)
                                scanForRegisterAnnotations(baseAnnotationPackage);
                                System.out.println("@Component相关注解:"+registerComponentAnnotations);
                                // 2. 扫描并注册组件类
                                scanAndRegisterComponents(basePackage);
                                System.out.println("相关bean对象:"+beanMap);
                                // 3. 执行依赖注入
                                injectDependencies();
                                System.out.println("执行依赖注入成功。。。");
                            }
                            /**
                             * 扫描所有自定义注解,筛出所有@Component(包括本身)相关注解
                             * @param baseAnnotationPackage:注解基础包(只扫描这个包)
                             */
                            private void scanForRegisterAnnotations(String baseAnnotationPackage) {
                                //把@Component本身加入
                                registerComponentAnnotations.add(Component.class);
                                //递归扫描所有被@Component修饰的注解
                                Reflections reflections = new Reflections(baseAnnotationPackage);
                                Set> componentWithComponentAnnotated = reflections.getTypesAnnotatedWith(registerComponentAnnotation);//注意不会识别嵌套注解
                        //            System.out.println(registerComponentAnnotation.getName()+":"+componentWithComponentAnnotated);
                                    for (Class clazz : componentWithComponentAnnotated) {
                                        //如果为一个普通类,则进行注册实例化
                                        if(!clazz.isInterface() && !clazz.isEnum() && !clazz.isPrimitive() && !clazz.isArray()){
                                            //进行实例化注册
                                            try {
                                                Object bean = clazz.getDeclaredConstructor().newInstance();
                                                beanMap.put(clazz, bean);  // 将类及其实例包装对象加入beanMap
                                            }catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                            //获取被@RestController修饰的类,后需要注册路由
                                                //注意下:这里就不递归检查了
                                            if(clazz.isAnnotationPresent(RestController.class)){
                                                restControllerClazzes.add(clazz);
                                            }else if (clazz.isAnnotationPresent(RestControllerAdvice.class)){//放全局异常处理器类
                                                restControllerAdviceClazzes.add(clazz);
                                            }
                                        }
                                    }
                                }
                            }
                            /**
                             * 执行依赖注入(简化后)
                             * 规则:需要为bean对象(被@Component修饰),会将被@Autowired修饰的属性注入
                             *  - 普通类型:直接根据类型注入
                             *  - 接口类型:当实现类只有一个时,会将这个注入
                             *
                             */
                            private void injectDependencies() {
                                //遍历所有bean对象(就是被@Component修饰)
                                for(Map.Entry interfaceType) {
                        //        Set
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码