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

1. 系统简介

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

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

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

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

2. 系统功能模块图

在这里插入图片描述

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图)
在这里插入图片描述

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-loaderbabel 编译工具支持 Vue 单文件组件的开发及 ES6 语法的转译,提升了开发效率和代码兼容性。

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

  • 辅助工具

    • html-webpack-plugin:生成自动引入依赖的 HTML 文件。
    • copy-webpack-plugin:用于静态资源的拷贝和处理。
    • friendly-errors-webpack-plugin:优化开发过程中的错误提示,提升开发体验。
  • 浏览器兼容性
    配置了 autoprefixerbrowserslist,自动添加浏览器前缀,确保兼容大多数主流浏览器。

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

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 容器自动识别并实例化控制器和全局异常处理类,并进行管理。
    • 容器提供依赖注入功能,确保控制器和异常处理器能通过反射机制正确地被调用。
  2. 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<String, Method> routes = new HashMap<>();//请求的路由映射
    private static final Map<String, Object> 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<Class<? extends Throwable>, Method> exceptionHandleMethod = new HashMap<>();//不同异常对应的请求方法
    private static final Map<Class<? extends Throwable>, Object> restControllerAdvices = new HashMap<>(); //全局异常处理类(来自IOC容器中的bean对象)
    //注意了下面也不管循环注解
    /**
     * 初始化全局异常处理方法
     * - 注册异常处理方法:如果为空,则获取方法参数类型(TODO:注意:这里规定异常处理方法参数)
     * - 注册异常处理对应的类
     * @param context:IOC容器
     */
    public static void initializeExceptionHandlers(ApplicationContext context) {
        for (Class<?> RestControllerAdviceClass : context.getRestControllerAdviceClazzes()) {
            Object restControllerAdvice = context.getBean(RestControllerAdviceClass);

            // 遍历方法,查找@ExceptionHandler标注的方法
            for (Method method : RestControllerAdviceClass.getDeclaredMethods()) {
                if (method.isAnnotationPresent(ExceptionHandler.class)) {
                    ExceptionHandler exceptionHandler = method.getAnnotation(ExceptionHandler.class);

                    // 获取注解中指定的异常类型,如果为空,则获取方法参数类型
                    Class<? extends Throwable>[] exceptionTypes = exceptionHandler.value();
                    if (exceptionTypes.length == 0) {
                        // 如果没有指定异常类型,则取方法的第一个参数类型作为异常类型
                        Parameter[] parameters = method.getParameters();
                        if (parameters.length > 0) {
                            Class<?> parameterType = parameters[0].getType();
                            if (Throwable.class.isAssignableFrom(parameterType)) {
                                exceptionTypes = new Class[]{(Class<? extends Throwable>) parameterType};
                            }
                        }
                    }

                    // 注册异常类型和方法的映射
                    for (Class<? extends Throwable> exceptionType : exceptionTypes) {
                        exceptionHandleMethod.put(exceptionType, method);
                        restControllerAdvices.put(exceptionType, restControllerAdvice); // 记录异常处理类实例
                    }
                }
            }
        }
        System.out.println("注册的异常处理方法:" + exceptionHandleMethod);
        System.out.println("注册的异常处理控制器:" + restControllerAdvices);
    }
    /////注册异常处理部分结束/////

    /**
     * 处理请求(Servlet那边的)
     * @param request
     * @param response
     * @throws IOException
     */
    public static void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String httpMethod = request.getMethod();//请求方式
        String relativePath = request.getRequestURI().substring(TomcatProperty.CONTEXT_PATH.length());//处理成类对应的请求路径
            //这里是存在上下文环境的,就是tomcat中会进行单独配置,所以最好就是把这个放在配置文件中
                //获取对应的处理类的路径
                    // 处理上下文路径为空的情况
                    //这时候访问:http://localhost:8083/api,上下文环境变为了"/api"
                    //如果在tomcat中配置了上下文是正常的
        String routeKey = httpMethod + relativePath;

        Method handlerMethod = routes.get(routeKey);
        Object restController = restControllers.get(routeKey);

        System.out.println("Requested Method and Path: " + httpMethod + " " + relativePath);
        System.out.println("处理方法:" + handlerMethod+"|"+"控制层对应的实例:"+restController);
        //(这里很重要)响应设置(在这里设置的话更好点,不用在多出设置)
        //1.json响应设置
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");

        //2.跨域问题设置:
        // 设置允许跨域的域名,*表示允许所有域名访问
        response.setHeader("Access-Control-Allow-Origin", "*");
        // 设置允许跨域的方法
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        // 设置允许跨域的请求头
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        // 是否允许携带凭证 (如 cookies)
        response.setHeader("Access-Control-Allow-Credentials", "true");


        if ("OPTIONS".equalsIgnoreCase(httpMethod)) {
            response.setStatus(HttpServletResponse.SC_OK);
            return;
        }


        //都不空才能处理请求
        if (handlerMethod != null && restController != null) {
            try {
                handlerMethod.setAccessible(true);//破坏访问属性
                Object[] args = resolveMethodParameters(handlerMethod, request);//处理请求获取控制层请求对应方法的参数
                System.out.println("转换后的参数:"+ JSON.toJSONString(args));
                Object result = handlerMethod.invoke(restController, args);//执行方法

                // JSON 响应处理
                String jsonResponse = JSONObject.toJSONString(result);
                response.getWriter().write(jsonResponse);
            } catch (Exception e) {
                System.out.println("成功捕获异常,具体信息:");
                e.printStackTrace();
//                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error processing request");
                handleException(response,e);
            }
        } else {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "Route not found");
        }
    }


    /**
     * 辅助方法:解析方请求的各种参数(主要为的是解析请求体参数和请求体内容)
     * 场景:处理请求获取控制层请求对应方法的参数
     * 规则:
     *  1. **`@RequestBody`**:用于接受 JSON 请求体,将其转换为方法参数对象。
     *  2. **`@RequestParam`**:用于从请求参数(URL 查询参数或表单参数)中获取特定的值并注入到方法参数中。
     *  3. **默认参数映射**:若没有指定注解,则按参数名匹配请求参数,类似 `@RequestParam` 的功能。
     * @param method:请求对应的处理方法
     * @param request:请求
     * @return
     * @throws IOException
     */
    private static Object[] resolveMethodParameters(Method method, HttpServletRequest request) throws IOException {
        Parameter[] parameters = method.getParameters();
        Object[] args = new Object[parameters.length];

        String requestBody = null;
        if ("POST".equalsIgnoreCase(request.getMethod()) || "PUT".equalsIgnoreCase(request.getMethod())) {
            requestBody = request.getReader().lines().reduce("", String::concat);
        }

        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (parameter.isAnnotationPresent(RequestBody.class)) {
                //TODO 探究:类型的转换这么整?
                //DONE 数组类型是否可行,数组也能直接转换,太牛了
                args[i] = JSON.parseObject(requestBody, parameter.getType());
//                args[i]=convertToType(requestBody,parameter.getType());   //这个有问题
                //TODO 本处处理泛型还需要细究去
                // 请求体:{数组名:[]},像这样还要不要解析为链表(感觉解析为数组会不会不合适)
                // 对应接口:/api/mall/settleAccounts
                // 根据参数名映射或者注解中的value
//                //处理泛型集合类型
//                if(List.class.equals(parameter.getType())){
//                    // 获取泛型参数类型 T
//                    Type genericType = ((ParameterizedType) parameter.getParameterizedType()).getActualTypeArguments()[0];
//                    // 首先将整个 JSON 请求体解析为 JSONObject
//                    JSONObject jsonObject = JSON.parseObject(requestBody);//注意请求体中一般被{}包住了,是一个对象
//                    // 使用 JSON 工具解析为 List<T>
////                    args[i] = JSON.parseArray(requestBody, (Class<?>) genericType);
//                }else{
//                    //非泛型解析
//                    args[i] = JSON.parseObject(requestBody, parameter.getType());
//
//                }
                //到这里结束
            } else if (parameter.isAnnotationPresent(RequestParam.class)) {
                //TODO 待解决:处理数组
                //DONE 待优化:当注解的value为空时,默认以参数名为主
                //  解决失败:发现参数名为arg0

                // 获取 @RequestParam 注解的 value,若为空,则使用参数名
                String paramName = parameter.getAnnotation(RequestParam.class).value();
                // 如果 value 为空,则默认使用参数名作为参数名
                if (paramName.isEmpty()) {
                    paramName = parameter.getName();  // 获取参数名作为默认参数名
                }
                String paramValue = request.getParameter(paramName);
                args[i] = convertToType(paramValue, parameter.getType());
            } else {
                String paramValue = request.getParameter(parameter.getName());
                args[i] = convertToType(paramValue, parameter.getType());
            }
        }
        return args;
    }

    /**
     * 辅助方法:利用全局异常处理器进行异常处理
     * @param response:响应
     * @param exception:发生的异常
     */
    private static void handleException(HttpServletResponse response, Exception exception) throws IOException {
        Throwable cause = exception.getCause() != null ? exception.getCause() : exception;

        // 找到能够处理的异常类型
        Class<? extends Throwable> handledExceptionType = findCouldHandleException(cause);

        // 如果找到对应的处理方法,则执行全局异常处理方法
        if (handledExceptionType != null) {
            Method handlerMethod = exceptionHandleMethod.get(handledExceptionType);
            Object handlerInstance = restControllerAdvices.get(handledExceptionType);

            try {
                // 调用异常处理方法
                Object result = handlerMethod.invoke(handlerInstance, cause);

                // 将结果转换为 JSON 格式并写入响应
                String jsonResponse = JSONObject.toJSONString(result);
                response.getWriter().write(jsonResponse);
//                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                response.setStatus(HttpServletResponse.SC_OK);//这里是这么定义,这里面用result的code来定义
            } catch (Exception e) {
                e.printStackTrace();
                // 如果异常处理失败,返回 500 错误
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error handling exception");
            }
        } else {
            // 如果没有找到处理器,返回 500 错误
            try {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unhandled exception");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



    /**
     * 辅助方法:循环查找父类,在全局异常处理器中找到能够处理的异常
     * @param ex 需要查找的异常
     * @return 能够处理的异常类,如果未找到则返回null
     */
    private static Class<? extends Throwable> findCouldHandleException(Throwable ex){
        Class<?> exceptionClass=ex.getClass();
        while(exceptionClass!=null){
            if(exceptionHandleMethod.containsKey(exceptionClass)){
                return exceptionClass.asSubclass(Throwable.class); // 返回能处理的异常类
                        //确保返回的类型与方法声明的返回类型 Class<? extends Throwable> 一致。
            }
            exceptionClass = exceptionClass.getSuperclass(); // 向上查找父类
                    //将每次循环时的 exceptionClass 更新为其父类,逐层向上查找。
        }
        return null;
    }


    //TODO 待优化:思考下,如果为长整型怎么办
    //  待看
    /**
     * 辅助方法:类型转换
     * @param value
     * @param type
     * @return
     */
    private static Object convertToType(String value, 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<Class<? extends Annotation>> registerComponentAnnotations = new HashSet<>();//存放被@Component修饰的注解
    private Map<Class<?>, Object> beanMap = new HashMap<>();  // key:类;value:实例;(单例模式)
        //这个类似工厂模式
    @Getter
    private Set<Class<?>> restControllerClazzes = new HashSet<>();//单独再拿出被@RestController修饰的类,后续还需要注册路由
    @Getter
    private Set<Class<?>> 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<Class<?>> annotations = reflections.getTypesAnnotatedWith(Component.class);
                //TODO:可能有问题:是否能扫描到多层嵌套的注解,虽然这里不知道为什么是能够扫描到的
//        System.out.println(annotations);
        for (Class<?> annotation : annotations) {
            //判断是否为注解
            if(annotation.isAnnotation()){
                //通过自定义的注解工具类递归判断是否被@Component修饰
                if(AnnotationUtils.isAnnotationPresent(annotation,Component.class,true)){//注意:这里使用正常判断是不会递归判断,只能看最外层
                    registerComponentAnnotations.add((Class<? extends Annotation>) annotation);
                }
            }
        }
    }

    /**
     * 扫描同时实例化注册bean对象
     * @param basePackage:当前需要扫描的包
     */
    private void scanAndRegisterComponents(String basePackage) {
        Reflections reflections = new Reflections(basePackage);//只扫描这个包
        //1. 遍历跟@Component相关的注解
        for(Class<? extends Annotation> registerComponentAnnotation:registerComponentAnnotations){
//            System.out.println("当前注解:"+registerComponentAnnotation);
            //2.获取被这个注解修饰的所有类同时进行判断是否需要实例话
            Set<Class<?>> 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<Class<?>,Object> entry: beanMap.entrySet()){
            Class<?> clazz = entry.getKey();
            Object bean = entry.getValue();

            //获取所有字段,检查是否有@Autowired注解
            for(Field field:clazz.getDeclaredFields()){
                if (field.isAnnotationPresent(Autowired.class)){
                    //1. 寻找其需要注入的依赖(实体)
                        //如果字段类型是接口,则需要根据接口类型获取实现类
                        //如果不是,则直接从注册的bean对象获取
                    Object dependency = null;
                        //获取字段类型
                    Class<?> fieldType=field.getType();
                    if(fieldType.isInterface()){
                        // 获取所有实现该接口的类
                        Set<Class<?>> implementors = getImplementors(fieldType);
                        //只能有一个实现,不然不好注入
                        if (implementors.size() == 1) {
                            //这里依然是从bean,而不是新建了
//                                Object dependency = implementors.iterator().next().getDeclaredConstructor().newInstance();//新建
                            dependency=beanMap.get(implementors.iterator().next());//TODO 探究:这里可以看看为什么不需要.getClass()
                        } else {
                            System.err.println("找到多个实现或者没有实现接口的类: " + fieldType.getName());
                        }
                    }else{
                        // 如果字段类型不是接口,直接从 beanMap 获取
                        dependency = beanMap.get(fieldType);
                    }
                    //2.注入(赋值)给这个字段
                    if (dependency != null) {
                        try {
                            field.setAccessible(true);
                            field.set(bean, dependency);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    } else {
                        // 如果找不到匹配的依赖
                        System.err.println("无法注入依赖: " + fieldType.getName());
                    }
                }
            }

        }

    }

    /**
     * 获取接口类型的所有实现类
     * @param interfaceType:接口的类型
     * @return:当前接口所有的实现类
     */
    private Set<Class<?>> getImplementors(Class<?> interfaceType) {
//        Set<Class<?>> implementors = new HashSet<>();
//        // 扫描所有已注册的 Bean,查找实现了该接口的类
//        for (Class<?> clazz : beanMap.keySet()) {
//            if (interfaceType.isAssignableFrom(clazz) && !clazz.equals(interfaceType)) {
//                implementors.add(clazz);
//            }
//        }
//        return implementors;
        //这里像试试流,作用跟上面一样
        return beanMap.keySet().stream()
                .filter(clazz->{
                    return interfaceType.isAssignableFrom(clazz) && !clazz.equals(interfaceType);
                        //`isAssignableFrom` 是 Java 反射 API 中的一个方法,判断 interfaceType(通常为接口或父类)是否是 clazz 的超类或超接口,或 clazz 是否可以赋值给 interfaceType 类型的变量。
                })
                .collect(Collectors.toSet());
    }


    /**
     * 获取bean对象
     * @param clazz
     * @return
     * @param <T>
     */
    public <T> T getBean(Class<T> clazz) {
        // 从 beanMap 中查找指定类的实例
        Object bean = beanMap.get(clazz);

        // 如果找不到该类型的 Bean,可以考虑抛出异常或返回 null
        if (bean == null) {
            throw new IllegalArgumentException("No bean found for class: " + clazz.getName());
        }

        return clazz.cast(bean); // 返回转换为指定类型的 bean
    }



}

4.2.2 数据库操作封装
4.2.2.1 功能概述

在本项目中,为了简化数据库操作并提高代码的可维护性,我们封装了一些数据库操作类和方法,主要涉及以下功能:

  1. 基本的数据库操作:
    • 使用 SQLExcutorUtil 类封装了常见的数据库操作方法,如查询单个对象、查询多个对象、查询标量值、更新操作、批量更新等。这些方法通过调用 QueryRunner 来执行 SQL 操作,简化了数据库的操作流程,调用者只需提供 SQL 语句和参数即可。
    • 主要方法包括:
      • querySingle:执行单条查询,返回一个对象。
      • queryList:执行查询,返回一个对象列表。
      • queryScalar:查询单个标量值(如计数或求和结果)。
      • update:执行更新、插入或删除操作。
      • updateAndGetGeneratedKey:执行插入操作,并返回生成的主键。
      • batchUpdate:执行批量更新操作。
      • transaction:执行事务操作,确保一组数据库操作原子性。
  2. SQL构造器:
    • BaseSQLBuilder 类作为所有 SQL 构造器的基类,封装了构建 SQL 查询、更新语句的通用逻辑。它支持动态添加查询条件(WHERE)、INBETWEEN 条件等。
    • 继承 BaseSQLBuilder类的具体 SQL 构造器:
      • SelectSQLBuilder:用于构建 SELECT 查询语句,支持字段选择、动态条件、排序、分页等。
      • UpdateSQLBuilder:用于构建 UPDATE 语句,支持动态设置更新字段和值,并自动拼接 SETWHERE 子句。

通过这种封装方式,开发人员可以更加简便、清晰地进行数据库操作,且代码具有良好的可扩展性与可维护性。在此基础上,未来可进一步增强对其他数据库操作(如 DELETEINSERT)的支持。

4.2.2.2 代码实现

核心思想:数据库操作的sql语句生成与执行分离

package com.wawu.common.utils;

import com.wawu.common.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.sql.*;
import java.util.List;

/**
 * 设计初衷:
 *  这个方法只管执行,其他都不管
 * 对于使用者来说:
 *  只需要要传入sql,参数,其他都不管
 */
public class SQLExcutorUtil {
    //获取数据库连接
    private static Connection getConnection() throws SQLException {
        return DruidUtil.getConnection();
    }

    //把参数放在最后就不用新建数组了
    //这里不使用泛型,达不到我想要的效果
    /**
     * 查询单个对象的方法
     * @param sql SQL语句
     * @param resultType 查询结果的类型
     * @param params 查询参数
     * @param <T> 返回的对象类型
     * @return 返回查询的单个结果
     * @throws SQLException
     */
    public static <T> T querySingle(String sql, Class<T> resultType, Object... params) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        try (Connection conn = getConnection()) {
            // 使用 BeanHandler 处理单一结果
            return queryRunner.query(conn, sql, new BeanHandler<>(resultType), params);
        }
    }
    /**
     * 查询多个对象的方法,返回一个列表
     * @param sql SQL语句
     * @param resultType 查询结果的类型
     * @param params 查询参数
     * @param <T> 返回的对象类型
     * @return 返回查询的结果列表
     * @throws SQLException
     */
    public static <T> List<T> queryList(String sql, Class<T> resultType, Object... params) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        try (Connection conn = getConnection()) {
            // 使用 BeanListHandler 处理多个结果
            return queryRunner.query(conn, sql, new BeanListHandler<>(resultType), params);
        }
    }

    /**
     * 返回单一的标量值
     * @param sql
     * @param resultType
     * @param params
     * @return
     * @param <T>
     * @throws SQLException
     */
    public static <T> T queryScalar(String sql, Class<T> resultType, Object... params) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        try (Connection conn = getConnection()) {
            return queryRunner.query(conn, sql, new ScalarHandler<>(), params);
        }
    }


    // 更新操作:INSERT、UPDATE、DELETE
    public static int update(String sql, Object... params) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        try (Connection conn = getConnection()) {
            return queryRunner.update(conn, sql, params);
        }
    }

    /**
     * (GPT生成未细看)
     * 执行更新语句,并返回生成的键值(insert)
     * @param sql
     * @param params
     * @return
     * @throws SQLException
     */
    public static long updateAndGetGeneratedKey(String sql, Object... params) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        try (Connection conn = getConnection()) {
            // 执行插入语句并获取生成的主键(假设主键是自增的)
            PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            // 设置参数
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);
            }
            // 执行更新
            int affectedRows = ps.executeUpdate();

            // 获取生成的主键
            if (affectedRows > 0) {
                try (ResultSet generatedKeys = ps.getGeneratedKeys()) {
                    if (generatedKeys.next()) {
                        return generatedKeys.getLong(1); // 返回生成的主键
                    }
                }
            }
            return -1; // 没有生成主键
        }
    }


    // 批量操作
    public static int[] batchUpdate(String sql, Object[][] params) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        try (Connection conn = getConnection()) {
            return queryRunner.batch(conn, sql, params);
        }
    }

    //TODO 这部分想应该不要放在这里最好,最好还是放在服务层
    // 事务操作(可选)
    public static void transaction(Runnable... operations) throws SQLException {
        try (Connection conn = getConnection()) {
            conn.setAutoCommit(false);
            try {
                for (Runnable operation : operations) {
                    operation.run(); // 执行操作
                }
                conn.commit(); // 提交事务
            } catch (SQLException e) {
                conn.rollback(); // 回滚事务
                throw e;
            }
        }
    }
}


package com.wawu.common.utils.SQLBuilder;

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

//TODO 细看:本处涉及到的代码都需要再次细看
//TODO 待完成:更新操作最好别用动态sql

/**
 * 此处必须是要按顺序构造,不然肯定是有问题的
 */
public abstract class BaseSQLBuilder {
    protected final StringBuilder sql;
    protected final List<Object> params;
    private boolean whereAdded = false;  // 标记是否已添加 WHERE 条件

    public BaseSQLBuilder(String baseQuery) {
        this.sql = new StringBuilder(baseQuery);
        this.params = new ArrayList<>();
    }

    /**
     * 添加通用条件
     */
    public BaseSQLBuilder addDynamicCondition(String fieldName, String operator, Object value) {
        if (value != null) {
            if (!whereAdded) { // 如果是第一个条件,添加 WHERE
                sql.append(" WHERE ");
                whereAdded = true;
            } else { // 后续的条件添加 AND
                sql.append(" AND ");
            }
            sql.append(fieldName).append(" ").append(operator).append(" ?");
            params.add("LIKE".equalsIgnoreCase(operator) ? "%" + value + "%" : value);
        }
        return this;
    }

    /**
     * 添加 IN 条件
     */
    public BaseSQLBuilder addInCondition(String fieldName, List<?> values) {
        if (values != null && !values.isEmpty()) {
            if (!whereAdded) { // 如果是第一个条件,添加 WHERE
                sql.append(" WHERE ");
                whereAdded = true;
            } else { // 后续的条件添加 AND
                sql.append(" AND ");
            }
            sql.append(fieldName).append(" IN (");
            for (int i = 0; i < values.size(); i++) {
                sql.append("?");
                if (i < values.size() - 1) sql.append(", ");
            }
            sql.append(")");
            params.addAll(values);
        }
        return this;
    }

    /**
     * 添加 BETWEEN 条件
     */
    public BaseSQLBuilder addBetweenCondition(String fieldName, Object start, Object end) {
        if (start != null && end != null) {
            if (!whereAdded) { // 如果是第一个条件,添加 WHERE
                sql.append(" WHERE ");
                whereAdded = true;
            } else { // 后续的条件添加 AND
                sql.append(" AND ");
            }
            sql.append(fieldName).append(" BETWEEN ? AND ?");
            params.add(start);
            params.add(end);
        }
        return this;
    }

    /**
     * 获取 SQL 语句
     */
    public String getSQL() {
        System.out.println(sql.toString());
        System.out.println(params);
        return sql.toString();
    }

    /**
     * 获取参数数组
     */
    public Object[] getParams() {
        return params.toArray();
    }

}
package com.wawu.common.utils.SQLBuilder;


import java.util.List;

public class SelectSQLBuilder extends BaseSQLBuilder {

    public SelectSQLBuilder(String tableName) {
        super("SELECT * FROM " + tableName);  // 不再加上 WHERE 1=1
    }

    //TODO 待优化:折中了下,跟父类有点冗余了
    // 严格来讲这里也不是继承
    // 这里的问题:就是添加动态条件调用的是父类的,但是返回的父类,但是如果还需要排序之类的都不行了
    @Override
    public SelectSQLBuilder addDynamicCondition(String fieldName, String operator, Object value) {
        super.addDynamicCondition(fieldName, operator, value);
        return this;
    }

    @Override
    public SelectSQLBuilder addInCondition(String fieldName, List<?> values) {
        super.addInCondition(fieldName, values);
        return this;
    }

    @Override
    public SelectSQLBuilder addBetweenCondition(String fieldName, Object start, Object end) {
        super.addBetweenCondition(fieldName, start, end);
        return this;
    }
    //到这里结束

    /**
     * 自定义查询字段
     */
    public SelectSQLBuilder selectFields(String... fields) {
        if (fields != null && fields.length > 0) {
            // 构建字段列表
            String columns = String.join(", ", fields);

            // 找到 SQL 中 "SELECT " 和 " FROM" 的位置,替换 SELECT * 为指定的列
            int selectIndex = sql.indexOf("SELECT ") + "SELECT ".length(); // 找到 SELECT 后的位置
            int fromIndex = sql.indexOf(" FROM"); // 找到 FROM 关键字的位置

            if (selectIndex > 0 && fromIndex > selectIndex) {
                sql.replace(selectIndex, fromIndex, columns);  // 替换 * 为实际字段
            }
        }
        return this;
    }


    /**
     * 添加分组支持
     */
    public SelectSQLBuilder addGroupBy(String... fields) {
        if (fields != null && fields.length > 0) {
            sql.append(" GROUP BY ").append(String.join(", ", fields));
        }
        return this;
    }

    /**
     * 添加排序支持
     */
    public SelectSQLBuilder addOrderBy(String sortField, String sortOrder) {
        if (sortField != null && !sortField.isEmpty()) {
            sql.append(" ORDER BY ").append(sortField).append(" ").append(sortOrder);
        }
        return this;
    }

    /**
     * 添加分页支持
     */
    public SelectSQLBuilder addLimit(int limit, int offset) {
        sql.append(" LIMIT ").append(limit).append(" OFFSET ").append(offset);
        return this;
    }
}
package com.wawu.common.utils.SQLBuilder;

import java.util.Map;

//TODO 待优化:加上反引号``,去避免关键字冲突
public class UpdateSQLBuilder extends BaseSQLBuilder {

    private boolean hasSetClause = false;

    public UpdateSQLBuilder(String tableName) {
        super("UPDATE " + tableName);
    }

    /**
     * 添加更新字段和值到 SET 子句(实时拼接到 sql)
     */
    public UpdateSQLBuilder setField(String fieldName, Object value) {
        if (fieldName != null && value != null) {
            if (!hasSetClause) {
                sql.append(" SET ");
                hasSetClause = true;
            } else {
                sql.append(", ");
            }
            sql.append(fieldName).append(" = ?");
            params.add(value);
        }
        return this;
    }

    /**
     * 添加多个更新字段和值
     */
    public UpdateSQLBuilder setFields(Map<String, Object> fields) {
        if (fields != null && !fields.isEmpty()) {
            fields.forEach(this::setField);
        }
        return this;
    }

    /**
     * 添加动态条件到 WHERE 子句
     */
    @Override
    public UpdateSQLBuilder addDynamicCondition(String fieldName, String operator, Object value) {
        super.addDynamicCondition(fieldName, operator, value);
        return this;
    }

    /**
     * 获取最终 SQL
     */
    @Override
    public String getSQL() {
        if (!hasSetClause) {
            throw new IllegalStateException("No fields specified for update. Please use setField() or setFields().");
        }
        // 调用父类的 getSQL 方法,完成 WHERE 子句拼接
        return super.getSQL();
    }
}

4.3 系统部分功能实现

系统有点大,本处只选取部分功能

4.3.1 登录注册功能

界面:

在这里插入图片描述
在这里插入图片描述

代码实现:

  • 前端部分:
<template>
  <div class="ClientLogin" :style="{width:width+'px',height:height+'px'}">
    <div class="content">
      <h3>MoreMall</h3>
      <div class="tag">
        <span @click="setIndex(0)" :class="{selected:curIndex===0}">登录</span>
        <span @click="setIndex(1)" :class="{selected:curIndex===1}">注册</span>
      </div>
      <div class="formBox" v-show="curIndex===0">
        <input ref="account" type="text" placeholder="账号" />
        <input ref="pwd" type="password" placeholder="密码" />
        <button @click="login">登录</button>
      </div>
      <div class="formBox" v-show="curIndex===1">
        <input ref="signEmail" type="text" placeholder="请输入注册的邮箱" />
        <input ref="signName" type="text" placeholder="请输入昵称" />
        <input ref="signPwd" type="password" placeholder="请输入密码" />
        <input ref="signRecipient" type="text" placeholder="请输入收件人姓名" />
        <input ref="signAddress" type="text" placeholder="请输入收件地址" />
        <input ref="signPhone" type="text" placeholder="请输入联系电话" />
        <button @click="signup">注册</button>
      </div>
    </div>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'
import {getClientSize} from '../../util/util';
import {login,signup} from '../../api/client';

export default {
  name: 'ClientLogin',
  computed:{
    width(){
      return getClientSize().width;
    },
    height(){
      return getClientSize().height;
    },
  },
  data () {
    return {
      curIndex:0
    }
  },
  methods:{
    ...mapMutations({
      setClientName: 'SET_CLIENT_NAME',
      setClientToken: 'SET_CLIENT_TOKEN'
    }),
    setIndex(index){
      if(index===this.curIndex){
        return;
      }
      this.curIndex = index;
    },
    login(){
      const account = this.$refs.account.value;
      const pwd = this.$refs.pwd.value;
      const res = login({
        account:account,
        pwd:pwd
      });
      res
      .then((data)=>{
        this.setClientName(data.name);
        this.setClientToken(data.token);
        this.$router.push('/');
      })
      .catch((e)=>{
        alert(e)
      })
    },
    signup(){
      const res = signup({
        email:this.$refs.signEmail.value,
        nickname:this.$refs.signName.value,
        pwd:this.$refs.signPwd.value,
        recipient:this.$refs.signRecipient.value,
        address:this.$refs.signAddress.value,
        phone:this.$refs.signPhone.value,
      });
      res
      .then((data)=>{
        this.setClientName(data.name);
        this.setClientToken(data.token);
        this.$router.push('/');
      })
      .catch((e)=>{
        console.error(e); // 打印错误对象到控制台
        alert(e.message || '注册失败'); // 显示错误信息
      })
    }
  }
}
</script>

<style scoped lang="less">
@import "../../assets/css/var.less";
.ClientLogin{
  background-color: @bgColor;
  position: relative;
  .content{
    width: 300px;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -260px;
    margin-left: -150px;
    text-align: center;
    overflow: hidden;
    h3{
      color:@secondColor;
      font-size: 50px;
    }
    .tag{
      margin-top: 20px;
      color:@fontDefaultColor;
      margin-bottom: 20px;
      span{
        display: inline-block;
        width: 50px;
        text-align: center;
        margin: 0 10px;
        padding: 10px 0;
        cursor: pointer;
      }
      .selected{
        border-bottom: 2px solid @secondColor;
        color:@secondColor
      }
    }
    input{
      border-radius: 0;
      box-shadow: none;
      background: #fff;
      padding: 14px;
      width: 80%;
      border: 1px solid @borderColor;
      margin-bottom: 10px;
    }
    button{
      width: 90%;
      background: @secondColor;
      box-shadow: none;
      border: 0;
      border-radius: 3px;
      line-height: 41px;
      color: #fff;
      cursor: pointer;
      margin-top: 20px;
    }
  }
}
</style>

  • 后端部分:

在登录功能中,用户提供 emailpassword,后端通过 UsersDAO 查询数据库验证用户是否存在,如果不存在则抛出异常。如果用户存在,使用 BCrypt 对密码进行校验,如果验证成功,生成一个包含用户 ID 的 JWT 令牌返回给前端。这个令牌将用于后续的身份认证。

注册功能的流程类似,用户提交注册信息后,系统会先检查 emailnickname 是否已经注册。如果没有,后端会对密码进行加密(使用 BCrypt)并保存用户数据到数据库。同时,系统生成一个 JWT 令牌,确保新用户可以立即登录。

在数据库交互部分,UsersDAO 通过构建动态 SQL 查询用户信息,支持根据 emailnickname 查找用户。如果用户未注册,执行插入操作,保存用户信息。密码存储时采用 BCrypt 加密算法,保证安全。

异常处理方面,通过自定义异常(如 LoginException)来捕获并返回相关错误信息,确保在登录或注册过程中出现错误时能够准确反馈给前端。

package com.wawu.server.controller;

import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.RestController;
import com.wawu.common.annotation.controller.mapping.PostMapping;
import com.wawu.common.annotation.controller.mapping.RequestMapping;
import com.wawu.common.annotation.controller.parameter.RequestBody;
import com.wawu.common.result.Result;
import com.wawu.pojo.dto.LoginDTO;
import com.wawu.pojo.entity.User;
import com.wawu.pojo.vo.LoginVO;
import com.wawu.server.service.UserService;

import java.sql.SQLException;

@RestController
@RequestMapping("/api/user")
public class UserController {
    @Autowired
    UserService userService;

    /**
     * 用户登录
     * @param loginDTO
     * @return
     */
    @PostMapping("/login")
//    @OptionsMapping("/login")
    public Result<LoginVO> login(@RequestBody LoginDTO loginDTO) throws SQLException {
        LoginVO loginVO=userService.login(loginDTO);
        return Result.success(loginVO);
    }

    /**
     * 注册用户(这里就不用dto了,跟数据库表都是对应的)
     * @param user
     * @return
     */
    @PostMapping("/signup")
    public Result<LoginVO> signup(@RequestBody User user) throws SQLException {
        LoginVO loginVO=userService.signup(user);
        return Result.success(loginVO);
    }
}
package com.wawu.server.service;

import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.Service;
import com.wawu.common.annotation.controller.parameter.RequestBody;
import com.wawu.common.constant.JwtClaimsConstant;
import com.wawu.common.constant.MessageConstant;
import com.wawu.common.exception.LoginException;
import com.wawu.common.property.JwtProperties;
import com.wawu.common.utils.JwtUtil;
import com.wawu.pojo.dto.LoginDTO;
import com.wawu.pojo.entity.User;
import com.wawu.pojo.vo.LoginVO;
import com.wawu.server.dao.UsersDAO;
import org.mindrot.jbcrypt.BCrypt;

import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@Service
public class UserService {
    @Autowired
    private UsersDAO usersDAO;

    //涉及到各种密码加密的一些东西
    int saltRounds = 5; // 定义盐的复杂度
    String salt = BCrypt.gensalt(saltRounds);

    public LoginVO login(@RequestBody LoginDTO loginDTO) throws SQLException {
        User user = usersDAO.getUser(User.builder().email(loginDTO.getAccount()).build());
        //账号没有注册
        if(user==null){
            throw new LoginException(MessageConstant.LOGIN_EMAIL_NOT_REGISTER);
        }
        System.out.println(loginDTO);
        System.out.println(user);
        LoginVO loginVO=new LoginVO();
        //校验密码
        if(BCrypt.checkpw(loginDTO.getPwd(),user.getPwd())){
            loginVO.setName(user.getNickname());
                //JWT令牌存储内容:用户id
            Map<String,Object> claims=new HashMap<>();//存放对应的的角色信息
            claims.put(JwtClaimsConstant.USER_ID,user.getId());
            loginVO.setToken(JwtUtil.createJWT(JwtProperties.secretKey,JwtProperties.ttl,claims));
            System.out.println(JwtUtil.parseJWT(JwtProperties.secretKey,loginVO.getToken()));
        }else{
            throw new LoginException(MessageConstant.LOGIN_PWD_NOT_ERROR);
        }

        return loginVO;
    }

    /**
     * 注册用户
     * @param newUser
     * @return
     */
    public LoginVO signup(User newUser) throws SQLException {
        //1. 判断是否已经被注册
        User user = usersDAO.getUser(User.builder()
                .email(newUser.getEmail()).build());
        if(user!=null){
            throw new LoginException(MessageConstant.LOGIN_EMAIL_HAD_REGISTER);
        }
        user = usersDAO.getUser(User.builder()
                .nickname(newUser.getNickname()).build());
        if(user!=null){
            throw new LoginException(MessageConstant.LOGIN_NICKNAME_HAD_REGISTER);
        }

        //2. 插入数据
        newUser.setCreatetime(LocalDateTime.now());
        newUser.setUpdatetime(LocalDateTime.now());
        newUser.setPwd(BCrypt.hashpw(newUser.getPwd(),salt));
        newUser.setId(usersDAO.insert(newUser));
        //3. 前端需要的返回数据
        LoginVO loginVO=new LoginVO();
        loginVO.setName(newUser.getNickname());
            //JWT令牌存储内容:用户id
        Map<String,Object> claims=new HashMap<>();//存放对应的的角色信息
        claims.put(JwtClaimsConstant.USER_ID,newUser.getId());
        loginVO.setToken(JwtUtil.createJWT(JwtProperties.secretKey,JwtProperties.ttl,claims));
        System.out.println(JwtUtil.parseJWT(JwtProperties.secretKey,loginVO.getToken()));
        return loginVO;
    }


    public static void main(String[] args) {
        //测试下bcrypt加密
        String pwd="123456";
        String dbPwd="$2a$10$SYwliJBnXvX65qfauY.MROLoKFn5VjfzrwqIgbWGAcUGEl/1K3Vde";//数据库中的密码(经过加密)
        int saltRounds = 5; // 定义盐的复杂度
        String salt = BCrypt.gensalt(saltRounds);
            //1.测试每次生成是否相同
        System.out.println(BCrypt.hashpw(pwd,salt));
        System.out.println(BCrypt.hashpw(pwd,salt));
        System.out.println(BCrypt.hashpw(pwd,salt));
                //测试结果为加密结果都相同
            //2.校验密码
        System.out.println(BCrypt.checkpw(pwd,dbPwd));
        System.out.println(BCrypt.checkpw(pwd,dbPwd));
        System.out.println(BCrypt.checkpw(pwd,dbPwd));
        pwd="654321";
        System.out.println(BCrypt.checkpw(pwd,dbPwd));
        System.out.println(BCrypt.checkpw(pwd,dbPwd));
        System.out.println(BCrypt.checkpw(pwd,dbPwd));

    }
}
package com.wawu.server.dao;

import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.User;

import java.sql.SQLException;
import java.util.List;

@Repository
public class UsersDAO {
    /**
     * 条件查询用户(现在只是用邮箱查)
     * @param userQuery
     * @return
     */
    public User getUser(User userQuery) throws SQLException {
        SelectSQLBuilder selectSqlBuilder =new SelectSQLBuilder("users")
                .addDynamicCondition("id","=",userQuery.getId())
                .addDynamicCondition("email","=", userQuery.getEmail())
                .addDynamicCondition("nickname","=",userQuery.getNickname());
        User user = SQLExcutorUtil.querySingle(selectSqlBuilder.getSQL(), User.class, selectSqlBuilder.getParams());
        return user;
    }

    /**
     * 条件查询用户(现在只是用邮箱查)
     * @param userQuery
     * @return
     */
    public List<User> getUsers(User userQuery) throws SQLException {
        SelectSQLBuilder selectSqlBuilder =new SelectSQLBuilder("users")
                .addDynamicCondition("email","=", userQuery.getEmail())
                .addDynamicCondition("nickname","=",userQuery.getNickname());
        List<User> users = SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(), User.class, selectSqlBuilder.getParams());
        return users;
    }


    //获取nickname
    public String getNickNameById(Long Id) throws SQLException {
        String sql="SELECT nickname FROM users WHERE id = ? LIMIT 1";
        return SQLExcutorUtil.queryScalar(sql,String.class,Id);
    }

    public User getUserById(Long Id) throws SQLException {
        SelectSQLBuilder sqlBuilder=new SelectSQLBuilder("users")
                .addDynamicCondition("id","=",Id);
        return SQLExcutorUtil.querySingle(sqlBuilder.getSQL(), User.class, sqlBuilder.getParams());
    }

    /**
     * 插入用户信息
     * @param user
     * @return
     */
    public Long insert(User user) throws SQLException {
        String sql="INSERT INTO `users` (`id`,`email`,`pwd`,`nickname`,`sex`,`recipient`,`address`,`phone`,`headimg`,`updatetime`,`createtime`) VALUES (DEFAULT,?,?,?,0,?,?,?,DEFAULT,?,?);";
        return SQLExcutorUtil.updateAndGetGeneratedKey(sql,user.getEmail(),user.getPwd(),user.getNickname(),user.getRecipient(),user.getAddress(),user.getPhone(),user.getUpdatetime(),user.getCreatetime());

    }


    public static void main(String[] args) throws SQLException {
        UsersDAO usersDAO=new UsersDAO();
        User userQuery=new User();
        userQuery.setEmail("2790440179");

        User user = usersDAO.getUser(userQuery);
        System.out.println(user);
    }
}

4.3.2 商品基础查询

页面:
在这里插入图片描述

代码实现:

  • 前端部分:
<template>
  <div class="Mall">
    <header>
      <div class="container clear">
        <span class="title" @click="navTo('/mall')">MoreMall 一站式选购平台</span>
        <NoticeList :notices="notices"/>
        <div class="right" v-if="clientToken">
          <span class="name">欢迎您,{{clientName}}</span>
          <span @click="navTo('/mall/personal')">个人中心</span>
          <span @click="logout">退出登录</span>
        </div>
        <div class="right" v-else>
          <span @click="navTo('/login')">登录</span>
          <span @click="navTo('/login')">注册</span>
        </div>
      </div>
    </header>
    <div class="content" :style="{minHeight:clientHeight+'px'}">
      <div class="container">
        <router-view></router-view>
      </div>
      <div class="fixedAd">
        <img src="../../assets/img/index2.gif" alt="" />
        <ul class="fixedList">
          <li>
            <i class="iconfont icon-collection_fill" />
            <span>新人有礼</span>
          </li>
          <li>
            <i class="iconfont icon-paixing-copy" />
            <span>热门商品</span>
          </li>
          <li>
            <i class="iconfont icon-fabulous" />
            <span>用户反馈</span>
          </li>
          <li @click="backToTop" v-show="shouldShowBT">
            <i class="iconfont icon-arrows-4-7" />
            <span>回顶部</span>
          </li>
        </ul>
      </div>
    </div>
    <div class="bottomInfo">
      <div class="container">
        <div class="service footerItem">
          <p class="title">客户服务</p>
          <span><i class="iconfont icon-people_fill" />在线客服</span>
          <span><i class="iconfont icon-fabulous" />用户反馈</span>
        </div>
        <div class="intro footerItem">
          <p class="title">何为MoreMall</p>
          <p class="intro-p">MoreMall原创生活类电商品牌,秉承网易一贯的严谨态度,我们深入世界各地,从源头全程严格把控商品生产环节,力求帮消费者甄选到优质的商品</p>
          <div>
            关注我们:
            <img src="http://yanxuan.nosdn.127.net/60068701f3a380911f237c26c91b39d0.png" alt=""/>
            <img src="http://yanxuan.nosdn.127.net/031e783d7ee645b6096980d0bf83079b.png" alt=""/>
            <img src="http://yanxuan.nosdn.127.net/0c8759a89cdbd7acf7f2921e6f0fad19.png" alt=""/>
          </div>
        </div>
        <div class="code footerItem">
          <p class="title">扫码下载APP</p>
          <img src="../../assets/img/code.png" alt=""/>
          <span>下载领1000元新人礼包</span>
        </div>
      </div>
    </div>
    <footer>
      <div class="container">
        <ul class="footerTop">
          <li>
            <img src="//yanxuan.nosdn.127.net/e6021a6fcd3ba0af3a10243b7a2fda0d.png" alt="" />
            <span>30天无忧退换货</span>
          </li>
          <li>
            <img src="//yanxuan.nosdn.127.net/e09c44e4369232c7dd2f6495450439f1.png" alt="" />
            <span>满88元免邮费</span>
          </li>
          <li>
            <img src="//yanxuan.nosdn.127.net/e72ed4de906bd7ff4fec8fa90f2c63f1.png" alt="" />
            <span>XX品质保证</span>
          </li>
        </ul>
        <div class="footerBottom">
          <ul>
            <li>关于我们</li>
            <li>帮助中心</li>
            <li>售后服务</li>
            <li>配送与验收</li>
            <li>商务合作</li>
            <li>企业采购</li>
            <li>开放平台</li>
            <li>搜索推荐</li>
            <li>友情链接</li>
          </ul>
          <p>XX公司版权所有 © 1996-2018   食品经营许可证:XXXXXXXXXXXXXXXXX</p>
        </div>
      </div>
    </footer>
  </div>
</template>

<script>
import { mapState,mapMutations } from 'vuex';
import NoticeList from '../../components/NoticeList';
import {getClientSize,backToTop} from '../../util/util';

export default {
  name: 'Mall',
  computed:{
    ...mapState([
      'clientToken',
      'clientName'
    ]),
  },
  components:{
    NoticeList
  },
  data () {
    return {
      notices:['今日疯抢:牛皮防水男靴仅229元!直减2...','【福利】领1000元APP新人礼'],
      clientHeight:getClientSize().height,
      shouldShowBT:false
    }
  },

  methods:{
    ...mapMutations({
      clientLogout: 'CLIENT_LOGOUT',
    }),
    navTo(route){
      this.$router.push(route)
    },
    logout(){
      this.clientLogout();
      this.$router.go(0);
    },
    backToTop(){
      backToTop();
    },
    watchScrollTop(){
      let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      if(scrollTop>150){
        this.shouldShowBT = true;
      }else{
        this.shouldShowBT = false;
      }
    }
  },

  mounted(){
    document.addEventListener('scroll',this.watchScrollTop,false);
  },

  beforeDestroyed(){
    document.removeEventListener('scroll',this.watchScrollTop,false);
  }
}
</script>

<style scoped lang="less">
@import "../../assets/css/var.less";
.Mall{
  width: 100%;
  header{
    width: 100%;
    background-color: #333333;
    height:38px;
    color:@fontShallowColor;
    user-select:none;
    z-index: 10000;
    position: absolute;
    left: 0;
    top: 0;
    .container{
      position: relative;
      height:38px;
      .title{
        position: absolute;
        left: 0;
        display: inline-block;
        height: 26px;
        top: 50%;
        margin-top: -13px;
        line-height: 26px;
        font-size: 14px;
        cursor: pointer;
      }
      .NoticeListBox{
        position: absolute;
        left: 200px;
      }
      .right{
        position: absolute;
        right: 0;
        display: inline-block;
        height: 26px;
        top: 50%;
        margin-top: -13px;
        line-height: 26px;
        font-size: 14px;
        span{
          margin-left: 20px;
          cursor: pointer;
        }
        .name{
          cursor: default;
        }
      }
    }
  }
  .content{
    padding-top: 40px;
  }
  .fixedAd{
    position: fixed;
    right: 0;
    top: 108px;
    width: 72px;
    img{
      display: block;
      width: 100%;
      height: 154px;
    }
    .fixedList{
      margin-top: 2px;
      background-color: white;
      width: 100%;
      li{
        width: 100%;
        height: 80px;
        text-align: center;
        border-bottom: 1px solid @borderColor;
        cursor: pointer;
        padding-top: 12px;
        i{
          display: block;
          font-size: 30px;
          color:#666666;
        }
        span{
          display: block;
          font-size: 14px;
          color:#666666;
          margin-top: 4px;
        }
        &:last-child{
          border-bottom: none;
        }
        &:hover{
          i{
            color:@thirdColor;
          }
          span{
            color:@thirdColor;
          }
        }
      }
    }
  }
  .bottomInfo{
    width: 100%;
    height: 300px;
    border-top: 1px solid @borderColor;
    overflow: hidden;
    margin-top: 80px;
    .footerItem{
      width: 33%;
      height: 210px;
      position: relative;
      top: 45px;
      display: inline-block;
      text-align: center;
      vertical-align: middle;
      color:@fontDefaultColor;
      .title{
        color: @fontDeepColor;
        margin-bottom: 30px;
      }
    }
    .service{
      border-right: 1px solid @borderColor;
      span{
        display: inline-block;
        width: 80px;
        height: 100px;
        border:1px solid @borderColor;
        text-align: center;
        margin: 0 10px;
        font-size: 14px;
        cursor: pointer;
        &:hover{
          color:@thirdColor;
        }
        i{
          display: block;
          font-size: 30px;
          margin-top: 20px;
          margin-bottom: 10px;
        }
      }
    }
    .intro{
      border-right: 1px solid @borderColor;
      .intro-p{
        font-size: 13px;
        width: 300px;
        margin: 0 auto;
        text-align: left;
        color:@fontDeepColor;
        line-height: 1.8em;
      }
      div{
        text-align: left;
        font-size: 14px;
        margin-left: 47px;
        margin-top: 20px;
        img{
          margin: 0 6px;
          display: inline-block;
          vertical-align: middle;
        }
      }
    }
    .code{
      img{
        display: block;
        margin: 0 auto;
      }
      span{
        font-size: 12px;
        color:@thirdColor;
        margin-top: 10px;
        display: block;
      }
    }
  }
  footer{
    width: 100%;
    height: 208px;
    background-color: #414141;
    color:white;
    overflow: hidden;
    .footerTop{
      padding: 36px 0;
      border-bottom: 1px solid #4f4f4f;
      width: 100%;
      li{
        display: inline-block;
        width: 33%;
        text-align: center;
        img{
          vertical-align: middle;
        }
        span{
          vertical-align: middle;
          font-size: 18px;
          margin-left: 10px;
        }
      }
    }
    .footerBottom{
      color:@fontDefaultColor;
      margin-top: 30px;
      font-size: 13px;
      text-align: center;
      ul{
        li{
          display: inline-block;
          cursor: pointer;
          padding: 0 6px;
          border-right: 2px solid @fontDefaultColor;
          &:last-child{
            border-right:none;
          }
        }
      }
      p{
        margin-top: 5px;
      }
    }
  }
}
</style>
  • 后端部分:
package com.wawu.server.controller;

import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.RestController;
import com.wawu.common.annotation.controller.mapping.DeleteMapping;
import com.wawu.common.annotation.controller.mapping.GetMapping;
import com.wawu.common.annotation.controller.mapping.PostMapping;
import com.wawu.common.annotation.controller.mapping.RequestMapping;
import com.wawu.common.annotation.controller.parameter.RequestBody;
import com.wawu.common.annotation.controller.parameter.RequestParam;
import com.wawu.common.result.Result;
import com.wawu.pojo.dto.AddOrderDTO;
import com.wawu.pojo.dto.AskGoodsMsgDTO;
import com.wawu.pojo.dto.SettleAccountsDTO;
import com.wawu.pojo.vo.GetGoodsCommentVO.GoodCommentVO;
import com.wawu.pojo.vo.GoodVO;
import com.wawu.pojo.vo.OrderVO.OrderVO;
import com.wawu.pojo.vo.getGoodsInfo.getGoodsInfoVO;
import com.wawu.pojo.vo.getGoodsMsg.getGoodsMsgVO;
import com.wawu.pojo.vo.searchGoodVO.goodVO;
import com.wawu.server.service.MallService;

import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;

@RestController
@RequestMapping("/api/mall")
public class MallController {
    @Autowired
    MallService mallService;


    /**
     * 通过商品类型获取商品列表
     * @param typeId
     * @return
     * @throws SQLException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @GetMapping("/getGoodsByType")
    public Result<List<GoodVO>> getGoodsByType(@RequestParam Long typeId) throws SQLException, InvocationTargetException, IllegalAccessException {
//    public Result<List<GoodVO>> getGoodsByType(@RequestParam Long typeId) throws SQLException, InvocationTargetException, IllegalAccessException {
        return Result.success(mallService.getGoodVOsByTypeId(typeId));
    }
}

package com.wawu.server.service;

import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.Service;
import com.wawu.common.constant.JwtClaimsConstant;
import com.wawu.common.constant.MessageConstant;
import com.wawu.common.exception.BaseException;
import com.wawu.common.property.JwtProperties;
import com.wawu.common.utils.JwtUtil;
import com.wawu.pojo.dto.AddOrderDTO;
import com.wawu.pojo.dto.AskGoodsMsgDTO;
import com.wawu.pojo.dto.CartDTO;
import com.wawu.pojo.entity.*;
import com.wawu.pojo.query.OrderQuery;
import com.wawu.pojo.vo.GetGoodsCommentVO.CommentVO;
import com.wawu.pojo.vo.GetGoodsCommentVO.GoodCommentVO;
import com.wawu.pojo.vo.GetGoodsCommentVO.UserVO;
import com.wawu.pojo.vo.GoodVO;
import com.wawu.pojo.vo.OrderVO.Goods;
import com.wawu.pojo.vo.OrderVO.OrderVO;
import com.wawu.pojo.vo.getGoodsInfo.getGoodsInfoVO;
import com.wawu.pojo.vo.getGoodsInfo.goodsDetailsVO;
import com.wawu.pojo.vo.getGoodsMsg.getGoodsMsgVO;
import com.wawu.pojo.vo.getGoodsMsg.replyVO;
import com.wawu.pojo.vo.searchGoodVO.goodVO;
import com.wawu.server.dao.*;
import io.jsonwebtoken.Claims;
import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
public class MallService {
    @Autowired
    GoodsDAO goodsDAO;
    @Autowired
    GoodsDetailsDAO goodsDetailsDAO;

    /**
     * 通过商品类型获取商品列表
     * @param typeId:(-1时表示查全部)
     * @return
     * @throws SQLException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public List<GoodVO> getGoodVOsByTypeId(Long typeId) throws SQLException, InvocationTargetException, IllegalAccessException {
//        List<Good> goods = goodsDAO.getGoodsByType(typeId==-1?null:typeId);
        List<Good> goods=goodsDAO.getGoods(Good.builder()
                .typeId(typeId==-1?null:typeId)//设置为空表示查全部
                .build());
        List<GoodVO> goodVOList=new ArrayList<>();
        for(Good good:goods){
            GoodVO goodVO=new GoodVO();
            BeanUtils.copyProperties(goodVO,good);
            //获取商品单价
            float priceByGoodsId = goodsDetailsDAO.getPriceByGoodsId(good.getId());
            goodVO.setPrice(priceByGoodsId);

            goodVOList.add(goodVO);
        }
        return goodVOList;
    }
   
}

package com.wawu.server.dao;

import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.Good;

import java.sql.SQLException;
import java.util.List;

@Repository
public class GoodsDAO {
    /**
     * 动态条件查询商品列表
     * @param goodQuery
     * @return
     * @throws SQLException
     */
    public List<Good> getGoods(Good goodQuery) throws SQLException {
        SelectSQLBuilder selectSqlBuilder =new SelectSQLBuilder("goods")
                .addDynamicCondition("typeId","=", goodQuery.getTypeId())
                .addDynamicCondition("id","=",goodQuery.getId())
                .addDynamicCondition("name","LIKE", goodQuery.getName())
                .addOrderBy("createtime","DESC");
        return SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(),Good.class, selectSqlBuilder.getParams());
    }
}

package com.wawu.server.dao;

import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.GoodDetail;

import java.sql.SQLException;
import java.util.List;

@Repository
public class GoodsDetailsDAO {
    //TODO 下面这个接口还需要在细究下,就是一个商品对应多个商品详情这个怎么算?
    /**
     * 根据商品获取单价
     * @param goodsId
     * @return
     * @throws SQLException
     */
    public float getPriceByGoodsId(Long goodsId) throws SQLException {
        String sql="SELECT unitPrice FROM goodsDetails WHERE goodsId = ? LIMIT 1";
        return SQLExcutorUtil.queryScalar(sql,Float.class,goodsId);
    }
}

4.3.3 订单条件查询

页面:

在这里插入图片描述

实现代码:

  • 前端部分:
<template>
  <div class="Orders">
    <header class="clear">
  		<span>订单管理</span>
  	</header>
    <div class="filters">
      <input type="text" v-model="filters.userId" placeholder="用户ID">
      <select v-model="filters.state">
        <option value="">全部</option>
        <option v-for="(label, index) in tags" :value="index-1" :key="index">{{label}}</option>
      </select>
      <input type="number" v-model="filters.minAmount" placeholder="最小金额">
      <input type="number" v-model="filters.maxAmount" placeholder="最大金额">
      <input type="date" v-model="filters.startDate">
      <input type="date" v-model="filters.endDate">
      <button @click="applyFilters">查询</button>
    </div>
<!--  	<Tag :tagList="tags" @indexChange="changeTag"/>-->
  	<div class="content">
  		<table class="ordersTable">
	        <thead>
	        	<tr><th>订单号</th><th>用户昵称</th><th>收件人</th><th>收货地址</th><th>联系电话</th><th>商品</th><th>规格</th><th>购买数量</th><th>金额</th><th>订单状态</th><th>更新时间</th><th>操作</th></tr>
	        </thead>
	        <tbody>
	            <tr v-for="(item,index) in orderList" :key="'order'+item.id">
	            	<td>{{item.id}}</td>
	            	<td>{{item.user.nickname}}</td>
	            	<td>{{item.user.name}}</td>
	            	<td>{{item.user.address}}</td>
	            	<td>{{item.user.phone}}</td>
	            	<td>{{item.goods}}</td>
	            	<td>{{item.spec}}</td>
	            	<td>{{item.num}}</td>
	            	<td>{{item.amount}}</td>
<!--	            	<td>{{item.state}}</td>-->
                <td>{{ getOrderState(item.state) }}</td>
	            	<td>{{item.time}}</td>
	                <td><button class="normal" @click="editOrder(item.id)">编辑</button><button class="delete" @click="deleteOrder(item.id)">删除</button></td>
	            </tr>
	        </tbody>
	    </table>
  	</div>
  </div>
</template>

<script>
import {getOrders,deleteOrder} from '../../api/admin';
import Tag from '../../components/Tag';
export default {
  name: 'Orders',
  components:{
  	Tag
  },
  computed:{
  },
  data(){
  	return{
      //各种查询条件
      filters: {
        userId: null,
        state: null,
        minAmount: null,
        maxAmount: null,
        startDate: null,
        endDate: null,
      },
  		tags:['全部','未付款','未发货','已发货','已到货'],
  		orderList:[]
  	}
  },
  methods:{
    applyFilters() {
      // 创建一个查询对象,动态去除空值或 null
      const params = {};
      if (this.filters.userId !== null && this.filters.userId !== "") {
        params.userId = this.filters.userId;
      }
      if (this.filters.state !== null && this.filters.state !== "") {
        params.state = this.filters.state;
      }
      if (this.filters.minAmount !== null && this.filters.minAmount !== "") {
        params.minAmount = this.filters.minAmount;
      }
      if (this.filters.maxAmount !== null && this.filters.maxAmount !== "") {
        params.maxAmount = this.filters.maxAmount;
      }
      if (this.filters.startDate !== null && this.filters.startDate !== "") {
        params.startDate = this.filters.startDate;
      }
      if (this.filters.endDate !== null && this.filters.endDate !== "") {
        params.endDate = this.filters.endDate;
      }

      // 发送请求时将 params 作为参数传递
      getOrders(params)
        .then((orders) => {
          this.orderList = orders;
        })
        .catch((e) => {
          alert(e);
        });
    },
    // 用来转换状态数字到对应的文本
    getOrderState(state) {
      const stateMap = {
        0: '未付款',
        1: '未发货',
        2: '已发货',
        3: '已到货'
      };
      return stateMap[state] || '未知状态'; // 默认返回"未知状态"
    },
  	changeTag(index){
  		const res = getOrders(index-1);
  		res
  		.then((orders)=>{
  			this.orderList = orders;
  		})
  		.catch((e)=>{
  			alert(e);
  		})
  	},
  	editOrder(id){
  		this.$router.push('/backstage/orders/'+id)
  	},
  	deleteOrder(id){
  		const res = deleteOrder(id);
  		res
  		.then(()=>{
  			alert('删除成功');
  			this.orderList.map((item,index)=>{
  				if(item.id===id){
  					this.orderList.splice(index,1);
  				}
  			})
  		})
  		.catch((e)=>{
  			alert(e);
  		})
  	}
  },
  mounted(){
  	this.changeTag(0);
  }
}
</script>

<style scoped lang="less">
@import "../../assets/css/var.less";
.Orders{
	header{
		width: 100%;
		height: 40px;
		line-height: 40px;
		span{
			float: left;
		}
	}
	.content{
		width: 100%;
		background-color: white;
		position: relative;
		top: -3px;
		padding: 5px;
		.ordersTable{
			width: 100%;
			th{
				text-align: center;
			}
			tbody{
				tr{
					td{
						max-width: 100px;
						min-width: 30px;
						text-align: center;
						button{
							display: block;
							overflow: hidden;
							margin-bottom: 5px;
						}
					}
				}
			}
		}
	}
  .filters {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 20px;

    input, select, button {
      padding: 5px;
      font-size: 14px;
    }
  }

}
</style>

  • 后端部分:

此处看的是/orders接口:

package com.wawu.server.controller;

import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.RestController;
import com.wawu.common.annotation.controller.mapping.DeleteMapping;
import com.wawu.common.annotation.controller.mapping.GetMapping;
import com.wawu.common.annotation.controller.mapping.PostMapping;
import com.wawu.common.annotation.controller.mapping.RequestMapping;
import com.wawu.common.annotation.controller.parameter.RequestBody;
import com.wawu.common.annotation.controller.parameter.RequestParam;
import com.wawu.common.result.Result;
import com.wawu.pojo.dto.AddTypeDTO;
import com.wawu.pojo.dto.GoodDTO;
import com.wawu.pojo.dto.LoginDTO;
import com.wawu.pojo.entity.GoodDetail;
import com.wawu.pojo.entity.GoodType;
import com.wawu.pojo.query.OrderQuery;
import com.wawu.pojo.vo.GetGoodsInfoByGoodsIdVO;
import com.wawu.pojo.vo.GetOrders.GetOrderVO;
import com.wawu.pojo.vo.GoodVO;
import com.wawu.pojo.vo.LoginVO;
import com.wawu.pojo.vo.UserVO;
import com.wawu.server.service.AdminService;
import com.wawu.server.service.MallService;

import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;

@RestController
@RequestMapping("/api/admin")
public class AdminController {
    @Autowired
    AdminService adminService;

    /**
     * 管理员端登录
     * @param loginDTO
     * @return
     */
    @PostMapping("/login")
    public Result<LoginVO> login(@RequestBody LoginDTO loginDTO) throws SQLException {
        return Result.success(adminService.login(loginDTO));
    }

    /**
     * 获取所有用户信息
     * @return
     */
    @GetMapping("/allUser")
    public Result<List<UserVO>> getAllUser() throws SQLException, InvocationTargetException, IllegalAccessException {
        return Result.success(adminService.getAllUser());
    }

    @GetMapping("/getType")
    public Result<List<GoodType>> getGoodTypes() throws SQLException {
        return Result.success(adminService.getGoodTypes());
    }

    /**
     * 添加商品类型
     * @param addTypeDTO
     * @return
     */
    @PostMapping("/addType")
    public Result addGoodType(@RequestBody AddTypeDTO addTypeDTO) throws SQLException {//这里只有一个属性,这里可以根据框架优化
        adminService.addGoodType(addTypeDTO.getName());
        return Result.success();
    }

    //TODO 看看吧:这里使用这个平替下
    @Autowired
    MallService mallService;
    /**
     * 通过商品类型id获取商品
     * @param typeId
     * @return
     * @throws SQLException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @GetMapping("/getGoodsByType")
    public Result<List<GoodVO>> getGoodVOsByTypeId(@RequestParam Long typeId) throws SQLException, InvocationTargetException, IllegalAccessException {
        return Result.success(mallService.getGoodVOsByTypeId(typeId));
    }

    /**
     * 添加商品
     * @param goodDTO
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws SQLException
     */
    @PostMapping("/addGoods")
    public Result addGood(@RequestBody GoodDTO goodDTO) throws InvocationTargetException, IllegalAccessException, SQLException {
        adminService.addGood(goodDTO);
        return Result.success();
    }

    /**
     * 更新商品
     * @param goodDTO
     * @return
     */
    @PostMapping("/updateGoods")
    public Result updateGood(@RequestBody GoodDTO goodDTO) throws InvocationTargetException, IllegalAccessException {
        adminService.updateGood(goodDTO);
        return Result.success();
    }

    /**
     * 删除商品
     * @param id
     * @return
     */
    @DeleteMapping("/deleteGoods")
    public Result deleteGoodById(@RequestParam Long id) throws SQLException {
        adminService.deleteGoodById(id);
        return Result.success();
    }

    /**
     * 获取商品详情
     * @param id
     * @return
     */
    @GetMapping("/getGoodsInfo")
    public Result<GetGoodsInfoByGoodsIdVO> getGoodsInfoByGoodsId(@RequestParam Long id) throws SQLException {
        return Result.success(adminService.getGoodsInfoByGoodsId(id));
    }


    /**
     * 添加商品详情(规格)
     * @param goodDetail
     * @return
     * @throws SQLException
     */
    @PostMapping("/addSpec")
    public Result<GoodDetail> addSpec(@RequestBody GoodDetail goodDetail) throws SQLException {
        return Result.success(adminService.addGoodDetail(goodDetail));
    }


    //TODO 后续有时间添加解析时间类型
    //TODO 带完善:删除商品可能会出现异常,这里需要改一下

    /**
     * 动态查询订单
     * @param userId
     * @param state
     * @param minAmount
     * @param maxAmount
     * @param startDate
     * @param endDate
     * @return
     */
    @GetMapping("/orders")
    public Result<List<GetOrderVO>> getOrders(
            @RequestParam("userId") Long userId,
            @RequestParam("state") Integer state,
            @RequestParam("minAmount") Double minAmount,
            @RequestParam("maxAmount") Double maxAmount,
            @RequestParam("startDate") LocalDateTime startDate,
            @RequestParam("endDate")  LocalDateTime endDate
    ) throws SQLException, InvocationTargetException, IllegalAccessException {
//        System.out.println(userId);
//        System.out.println(state);
//        System.out.println(minAmount);
//        System.out.println(maxAmount);
//        System.out.println(startDate);
//        System.out.println(endDate);
        return Result.success(adminService.getOrders(
                OrderQuery.builder()
                        .userId(userId)
                        .state(state==null||state == -1 ?null:state)
                        .minAmount(minAmount)
                        .maxAmount(maxAmount)
                        .startDate(startDate)
                        .endDate(endDate)
                        .build()
        ));
    }


}

package com.wawu.server.service;

import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.Service;
import com.wawu.common.constant.JwtClaimsConstant;
import com.wawu.common.constant.MessageConstant;
import com.wawu.common.exception.BaseException;
import com.wawu.common.property.JwtProperties;
import com.wawu.common.utils.JwtUtil;
import com.wawu.pojo.dto.GoodDTO;
import com.wawu.pojo.dto.LoginDTO;
import com.wawu.pojo.entity.*;
import com.wawu.pojo.query.OrderQuery;
import com.wawu.pojo.vo.GetGoodsInfoByGoodsIdVO;
import com.wawu.pojo.vo.GetOrders.GetOrderUserVO;
import com.wawu.pojo.vo.GetOrders.GetOrderVO;
import com.wawu.pojo.vo.LoginVO;
import com.wawu.pojo.vo.UserVO;
import com.wawu.server.dao.*;
import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 处理管理员的服务
 */
@Service
public class AdminService {
    @Autowired
    GoodTypesDAO goodTypesDAO;
    @Autowired
    AdminsDAO adminsDAO;
    @Autowired
    UsersDAO usersDAO;
    @Autowired
    GoodsDAO goodsDAO;
    @Autowired
    GoodsDetailsDAO goodsDetailsDAO;
    @Autowired
    OrdersDAO ordersDAO;
    public List<GoodType> getGoodTypes() throws SQLException {
        return goodTypesDAO.getGoodTypes();
    }

    /**
     * 管理员登录
     * (此处数据库中没有加密)
     * @param loginDTO
     * @return
     */
    public LoginVO login(LoginDTO loginDTO) throws SQLException {
        //此处能够顺便校验密码
        List<Admin> admins =adminsDAO.getAdmin(Admin.builder()
                .account(loginDTO.getAccount())
                .pwd(loginDTO.getPwd()).build());
        if(admins ==null|| admins.isEmpty()){
            throw new BaseException(MessageConstant.LOGIN_ACOUNT_NOT_EXIST);
        }
        Admin admin=admins.get(0);
        LoginVO loginVO=new LoginVO();
        loginVO.setName(admin.getName());
        Map<String,Object> claims=new HashMap<>();
        claims.put(JwtClaimsConstant.ADMIN_ID,admin.getId());
        loginVO.setToken(JwtUtil.createJWT(JwtProperties.secretKey,JwtProperties.ttl,claims));
        return loginVO;

    }

    //TODO 待优化此处没有判重
    /**
     * 添加商品类型
     * @param typeName
     */
    public void addGoodType(String typeName) throws SQLException {
        goodTypesDAO.insert(typeName);
    }

    /**
     * 获取所有的用户信息
     * @return
     */
    public List<UserVO> getAllUser() throws SQLException, InvocationTargetException, IllegalAccessException {
        List<UserVO> userVOS=new ArrayList<>();
        for(User user:usersDAO.getUsers(new User())){
            UserVO userVO=new UserVO();
            BeanUtils.copyProperties(userVO,user);
            userVOS.add(userVO);
        }
        return userVOS;
    }


    //TODO 设置为事务
    /**
     * 添加商品(涉及到商品详情)
     * @param goodDTO
     */
    public void addGood(GoodDTO goodDTO) throws InvocationTargetException, IllegalAccessException, SQLException {
        Good good=new Good();
        BeanUtils.copyProperties(good,goodDTO);
        good.setCreatetime(LocalDateTime.now());
        good.setUpdatetime(LocalDateTime.now());
        good.setId(goodsDAO.insert(good));
        for (GoodDetail goodDetail : goodDTO.getSpecList()) {
            goodDetail.setGoodsId(good.getId());
            goodDetail.setCreateTime(LocalDateTime.now());
            goodDetail.setUpdateTime(LocalDateTime.now());
            goodsDetailsDAO.insert(goodDetail);
        }
    }

    /**
     * 更新商品
     * @param goodDTO
     */
    public void updateGood(GoodDTO goodDTO) throws InvocationTargetException, IllegalAccessException {
        Good good=new Good();
        BeanUtils.copyProperties(good,goodDTO);
        good.setUpdatetime(LocalDateTime.now());
        goodsDAO.update(good);
        for (GoodDetail goodDetail : goodDTO.getSpecList()) {
            goodDetail.setUpdateTime(LocalDateTime.now());
            goodDetail.setCreateTime(null);//这里前端不知道为啥设置了
            goodsDetailsDAO.update(goodDetail);
        }
    }

    /**
     * 通过商品类型删除商品,包括商品详情
     * @param goodId
     */
    public void deleteGoodById(Long goodId) throws SQLException {
        goodsDAO.deleteById(goodId);
        goodsDetailsDAO.deleteByGoodId(goodId);
    }

    /**
     * 获取商品详情
     * @param goodsId
     * @return
     */
    public GetGoodsInfoByGoodsIdVO getGoodsInfoByGoodsId(Long goodsId) throws SQLException {
        List<Good> goods = goodsDAO.getGoods(Good.builder()
                .id(goodsId).build());
        if(goods==null||goods.isEmpty()){
            throw new BaseException(MessageConstant.GOOD_NOT_EXIST);
        }
        Good good=goods.get(0);
        GetGoodsInfoByGoodsIdVO getGoodsInfoByGoodsIdVO=new GetGoodsInfoByGoodsIdVO();
        getGoodsInfoByGoodsIdVO.setGoods(good);
        //获取商品详情(规格)
        List<GoodDetail> goodDetails = goodsDetailsDAO.getGoodDetails(GoodDetail.builder()
                .goodsId(goodsId).build());
        getGoodsInfoByGoodsIdVO.setSpecs(goodDetails);
        return getGoodsInfoByGoodsIdVO;
    }

    /**
     * 添加商品详情
     * @param goodDetail
     * @return
     */
    public GoodDetail addGoodDetail(GoodDetail goodDetail) throws SQLException {
        goodDetail.setCreateTime(LocalDateTime.now());
        goodDetail.setUpdateTime(LocalDateTime.now());
        goodDetail.setId(goodsDetailsDAO.insert(goodDetail));
        return goodDetail;
    }


    //TODO 这里其实有很多业务需要优化但是没时间了,不管了
    /**
     * 条件查询订单
     * @param orderQuery
     * @return
     */
    public List<GetOrderVO> getOrders(OrderQuery orderQuery) throws SQLException, InvocationTargetException, IllegalAccessException {
        List<GetOrderVO> getOrderVOS=new ArrayList<>();
        for (Order order : ordersDAO.getOrders(orderQuery)) {
            GetOrderVO getOrderVO=new GetOrderVO();
            BeanUtils.copyProperties(getOrderVO,order);
            getOrderVO.setTime(order.getUpdateTime());

            //查询用户表
            User user = usersDAO.getUser(User.builder()
                    .id(order.getUserId()).build());
            if (user==null){
                user=new User();
                user.setNickname("已注销用户");
                user.setRecipient("已注销用户");
                user.setAddress("已注销用户");
                user.setPhone("已注销用户");
            }
            GetOrderUserVO getOrderUserVO=new GetOrderUserVO();
            BeanUtils.copyProperties(getOrderUserVO,user);
            getOrderUserVO.setName(user.getRecipient());
            getOrderVO.setUser(getOrderUserVO);


            //查询商品详情表获取规格
            List<GoodDetail> goodDetails = goodsDetailsDAO.getGoodDetails(GoodDetail.builder()
                    .id(order.getGoodsDetailId()).build());
            GoodDetail goodDetail;
            if(goodDetails==null||goodDetails.isEmpty()){
                throw new BaseException(MessageConstant.GOOD_DETAIL_NOT_EXIST);
//                goodDetail=new GoodDetail();
//                goodDetail.setGoodsId();
            }else{
                goodDetail = goodDetails.get(0);
            }

            getOrderVO.setSpec(goodDetail.getSpecName());

            //查询商品表获取商品名字
            Good goodsById = goodsDAO.getGoodsById(goodDetail.getGoodsId());
            getOrderVO.setGoods(goodsById.getName());

            getOrderVOS.add(getOrderVO);
        }
        return getOrderVOS;

    }
}

package com.wawu.server.dao;

import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.Order;
import com.wawu.pojo.query.OrderQuery;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

@Repository
public class OrdersDAO {
    /**
     * 动态条件查询订单
     * @param orderQuery
     * @return
     */
    public List<Order> getOrders(OrderQuery orderQuery) throws SQLException {
        SelectSQLBuilder selectSqlBuilder = new SelectSQLBuilder("orders")
                .addDynamicCondition("id","=",orderQuery.getId())
                .addDynamicCondition("userId", "=", orderQuery.getUserId())
                .addDynamicCondition("state", "=", orderQuery.getState())
                .addBetweenCondition("updatetime",orderQuery.getStartDate(),orderQuery.getEndDate())
                .addBetweenCondition("amount",orderQuery.getMinAmount(),orderQuery.getMaxAmount())
                .addOrderBy("updatetime","DESC");
        return SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(), Order.class, selectSqlBuilder.getParams());
    }


}

package com.wawu.server.dao;

import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.User;

import java.sql.SQLException;
import java.util.List;

@Repository
public class UsersDAO {

    /**
     * 条件查询用户(现在只是用邮箱查)
     * @param userQuery
     * @return
     */
    public List<User> getUsers(User userQuery) throws SQLException {
        SelectSQLBuilder selectSqlBuilder =new SelectSQLBuilder("users")
                .addDynamicCondition("email","=", userQuery.getEmail())
                .addDynamicCondition("nickname","=",userQuery.getNickname());
        List<User> users = SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(), User.class, selectSqlBuilder.getParams());
        return users;
    }
}

package com.wawu.server.dao;

import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.GoodDetail;

import java.sql.SQLException;
import java.util.List;

@Repository
public class GoodsDetailsDAO {


    /**
     * 动态条件查询商品详情表
     * @param goodDetailQuery
     * @return
     */
    public List<GoodDetail> getGoodDetails(GoodDetail goodDetailQuery) throws SQLException {
        SelectSQLBuilder selectSqlBuilder = new SelectSQLBuilder("goodsDetails")
                .addDynamicCondition("id", "=", goodDetailQuery.getId())
                .addDynamicCondition("goodsId", "=", goodDetailQuery.getGoodsId());
        return SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(),GoodDetail.class, selectSqlBuilder.getParams());

    }
}

package com.wawu.server.dao;

import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.Good;

import java.sql.SQLException;
import java.util.List;

@Repository
public class GoodsDAO {
    //获取商品资料
    public Good getGoodsById(Long id) throws SQLException {
        SelectSQLBuilder sqlBuilder=new SelectSQLBuilder("goods")
                .addDynamicCondition("id","=", id)
                .addOrderBy("createtime","DESC");
        return SQLExcutorUtil.querySingle(sqlBuilder.getSQL(),Good.class,sqlBuilder.getParams());
    }
}

4.4 页面总览

这里转存失败了,具体就看项目仓库里的,里面有图片

5. 小结

在开发本商城系统的过程中,我深刻体会到了团队协作、技术选型和架构设计对系统成功的关键作用。

首先,团队合作和沟通是整个项目顺利进行的基础。通过使用 Git 和 Gitee,我们能够高效地管理代码和协同开发。版本控制不仅帮助我们在不同开发者之间同步代码,还避免了冲突,保障了开发进度的顺利推进。与此同时,ApiFox 的使用确保了前后端接口的一致性,减少了因接口差异而导致的问题,提升了开发效率和准确性。

其次,项目的技术选型和架构设计让我深刻感受到了简洁与高效的重要性。在后端,我选择了 Servlet 技术与自定义注解,虽然看起来相对传统,但通过合理的设计和反射机制,简化了开发工作,减少了不必要的配置与依赖。这种思路让我意识到,很多时候简单的技术实现往往能解决复杂的业务需求,尤其是在团队项目中,技术的“简洁性”是保证高效协作的前提。

在前端部分,Vue.js 和 axios 的使用让我意识到,前后端分离的开发模式不仅提升了系统的可扩展性,还能使前端开发人员专注于界面与用户体验的优化,而后端则专注于业务逻辑。这种分工让开发过程更加高效,并且让整个系统的维护和扩展变得更加方便。

在安全性方面,JWT 和 BCrypt 技术的应用让我深刻理解到,安全是系统的基石。对用户信息的保护不仅仅是技术上的挑战,更是对用户信任的保证。通过加密算法保护密码和使用 token 验证身份的方式,增强了系统的安全性,也为用户提供了更加可靠的保障。

通过这次开发,我更加认识到在系统设计和开发过程中,灵活的技术选型、清晰的架构设计以及团队成员之间的有效合作,能共同促进一个高效、可扩展、易维护的系统的诞生。
arams());
return users;
}
}




```java
package com.wawu.server.dao;

import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.GoodDetail;

import java.sql.SQLException;
import java.util.List;

@Repository
public class GoodsDetailsDAO {


    /**
     * 动态条件查询商品详情表
     * @param goodDetailQuery
     * @return
     */
    public List<GoodDetail> getGoodDetails(GoodDetail goodDetailQuery) throws SQLException {
        SelectSQLBuilder selectSqlBuilder = new SelectSQLBuilder("goodsDetails")
                .addDynamicCondition("id", "=", goodDetailQuery.getId())
                .addDynamicCondition("goodsId", "=", goodDetailQuery.getGoodsId());
        return SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(),GoodDetail.class, selectSqlBuilder.getParams());

    }
}

package com.wawu.server.dao;

import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.Good;

import java.sql.SQLException;
import java.util.List;

@Repository
public class GoodsDAO {
    //获取商品资料
    public Good getGoodsById(Long id) throws SQLException {
        SelectSQLBuilder sqlBuilder=new SelectSQLBuilder("goods")
                .addDynamicCondition("id","=", id)
                .addOrderBy("createtime","DESC");
        return SQLExcutorUtil.querySingle(sqlBuilder.getSQL(),Good.class,sqlBuilder.getParams());
    }
}

4.4 页面总览

转存失败了具体看仓库地址里的吧,根据图片名搜索就能找到

#### 4.4.1 管理端

![管理端-登录](images/管理端-登录.png)

![管理端-商品查询](images/管理端-商品查询.png)

![管理端-编辑商品页面](images/管理端-编辑商品页面-17339991708812.png)

![管理端-添加类别](images/管理端-添加类别.png)

![管理端-订单管理页面](images/管理端-订单管理页面-17339992406153.png)



#### 4.4.2 购物端

![image-20241212183034696](images/image-20241212183034696.png)

![image-20241212180813705](images/image-20241212180813705.png)

![image-20241212182913530](images/image-20241212182913530.png)



![image-20241212182952277](images/image-20241212182952277.png)



![image-20241212183004575](images/image-20241212183004575.png)





![image-20241212183051474](images/image-20241212183051474.png)

![image-20241212183100260](images/image-20241212183100260.png)

5. 小结

在开发本商城系统的过程中,我深刻体会到了团队协作、技术选型和架构设计对系统成功的关键作用。

首先,团队合作和沟通是整个项目顺利进行的基础。通过使用 Git 和 Gitee,我们能够高效地管理代码和协同开发。版本控制不仅帮助我们在不同开发者之间同步代码,还避免了冲突,保障了开发进度的顺利推进。与此同时,ApiFox 的使用确保了前后端接口的一致性,减少了因接口差异而导致的问题,提升了开发效率和准确性。

其次,项目的技术选型和架构设计让我深刻感受到了简洁与高效的重要性。在后端,我选择了 Servlet 技术与自定义注解,虽然看起来相对传统,但通过合理的设计和反射机制,简化了开发工作,减少了不必要的配置与依赖。这种思路让我意识到,很多时候简单的技术实现往往能解决复杂的业务需求,尤其是在团队项目中,技术的“简洁性”是保证高效协作的前提。

在前端部分,Vue.js 和 axios 的使用让我意识到,前后端分离的开发模式不仅提升了系统的可扩展性,还能使前端开发人员专注于界面与用户体验的优化,而后端则专注于业务逻辑。这种分工让开发过程更加高效,并且让整个系统的维护和扩展变得更加方便。

在安全性方面,JWT 和 BCrypt 技术的应用让我深刻理解到,安全是系统的基石。对用户信息的保护不仅仅是技术上的挑战,更是对用户信任的保证。通过加密算法保护密码和使用 token 验证身份的方式,增强了系统的安全性,也为用户提供了更加可靠的保障。

通过这次开发,我更加认识到在系统设计和开发过程中,灵活的技术选型、清晰的架构设计以及团队成员之间的有效合作,能共同促进一个高效、可扩展、易维护的系统的诞生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值