2024.10月7~10日 进一步完善《电信资费管理系统》

 一、新增的模块:

在原项目基础上,新增加了以下功能:

1、增加AspectJ 框架的AOP 异常记录和事务管理模块。

2、增加SpringMVC的拦截器,实现登录 控制页面访问权限。

3、增加 Logback日志框架,记录日志。

4、增加动态验证码(CAPTCHA)并验证功能。

5、增加所有页面的表单验证功能。

二、完善项目结构:

前端项目结构无需改变。

更新后端结构:

aop包:aop包下是ExceptionLogAspect.java切面类,实现了 异常记录和记录日志、事务管理等功能

interceptor包:包含RoleInterceptor.java拦截器,实现了根据登录身份进行权限检查从而实现控制访问不同登录角色的界面

  

三、新增模块实现:

1、AspectJ 框架AOP 事务管理 和  记录日志

(1)pom.xml中添加依赖坐标:(AspectJ和Logback)

<!-- AspectJ -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.19</version>
</dependency>

<!-- 日志框架(SLF4J 和 Logback) -->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.36</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.11</version>
</dependency>

(2)日志配置文件:logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--CONSOLE:将日志输出到控制台。-->

<!--FILE:将日志输出到文件 logs/application.log,并按天滚动生成新的日志文件。-->

<!--root:设置根日志级别为 INFO,并绑定到 CONSOLE 和 FILE Appender。-->

<!--logger:为 ExceptionLogAspect 类单独设置日志级别为 ERROR,仅记录错误日志。-->
<configuration>

    <!-- Console Appender -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 文件Appender -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天生成一个新的日志文件 -->
            <fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保留30天的历史日志 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 日志级别和Appender绑定 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>

    <!-- 为AOP切面单独设置日志级别-->
    <logger name="com.ssm.netctoss.aop.ExceptionLogAspect" level="ERROR" additivity="false">
        <appender-ref ref="FILE" />
    </logger>

</configuration>

(3)在spring-ioc.xml配置文件中,新增配置SpringMVC拦截器:

http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
<!-- 启用AOP -->
<aop:aspectj-autoproxy/>

<!-- 启用事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

 完整的spring-ioc配置代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           https://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop
           https://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/tx
           https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 开启注解扫描,比如扫描Dao, Service, AOP切面 -->
    <context:component-scan base-package="com.ssm.netctoss"/>

    <!-- 配置加载jdbc.properties文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 配置druid的连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 配置SqlSessionFactory的bean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 绑定数据源 -->
        <property name="dataSource" ref="dataSource"></property>
        <!-- 加载mybatis的核心配置文件 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <!-- 配置加载各个pojo对应的XXXXMapper.xml -->
        <property name="mapperLocations"  value="classpath:com/ssm/netctoss/mapper/*.xml"/>

        <property name="typeAliasesPackage" value="com.ssm.netctoss.pojo"/>
    </bean>

    <!-- 配置可以扫描mapper/dao接口的类型 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
        <property name="basePackage" value="com.ssm.netctoss.mapper"></property>
    </bean>

    <!-- 启用AOP -->
    <aop:aspectj-autoproxy/>

    <!-- 启用事务管理 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

(4)举例:用service层进行切面:

建立aop包,包中建立 ExceptionLogAspect.java切面类:

/**
 * 异常记录切面
 * @Aspect:标记该类为切面。
 *
 * @Component:将切面类注册为 Spring Bean。
 *
 * @AfterThrowing:在目标方法抛出异常后执行通知。
 *
 * pointcut:匹配 com.ssm.netctoss.service 包及其子包中的所有方法。
 *
 * logAfterThrowing:记录异常信息,包括方法名和异常详情。
 */
@Aspect
@Component
public class ExceptionLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionLogAspect.class);

    /**
     * 定义切点:匹配 service 包及其子包中的所有方法
     */
    @AfterThrowing(pointcut = "execution(* com.ssm.netctoss.service..*(..))", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
        // 获取方法签名
        String methodName = joinPoint.getSignature().toShortString();
        // 获取异常信息
        String errorMessage = ex.getMessage();
        // 记录日志
        logger.error("Exception in {}: {}", methodName, errorMessage, ex);
    }
}

解释:

     使用Spring AOP实现的异常日志记录切面。它定义了一个切点,匹配com.ssm.netctoss.service包及其子包中的所有方法。这些方法抛出异常时,切面会记录异常日志。

     切点定义使用了@AfterThrowing注解,它表示在方法抛出异常后执行。pointcut属性指定了切点,用于匹配需要记录异常日志的方法。throwing属性指定了接收异常的参数名,这里命名为ex

  logAfterThrowing方法是切面的通知方法,它接收两个参数:JoinPoint对象和异常对象。JoinPoint对象包含了方法签名等信息,可以通过调用它的getSignature()方法获取方法签名。异常对象可以通过ex参数获取。

logAfterThrowing方法中,首先获取方法签名,然后获取异常信息,并将其记录到日志中。这里使用了LoggerFactory获取一个Logger对象,用于记录日志。logger.error方法用于记录错误日志,它接收三个参数:日志前缀、日志信息和异常对象。

    注意,切面类需要使用@Aspect@Component注解进行标注,以便Spring容器能够扫描到并将其注册为Bean。

2、SpringMVC拦截器,实现登录控制页面访问权限 。

(1)spring-mvc.xml添加 springmvc拦截器配置:

<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 指定需要拦截的URL路径 -->
        <mvc:mapping path="/role/**"/>
        <mvc:mapping path="/admin/**"/>
        <!-- 定义拦截器的Bean -->
        <bean class="com.ssm.netctoss.interceptor.RoleInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

 完整的spring-mvc.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--开启SpringMVC的注解: 简化处理器映射器和处理器适配器的bean注册,以及JSON数据的传递-->
    <mvc:annotation-driven/>

    <!--静态资源的访问通过配置-->
    <mvc:default-servlet-handler/>

    <!--视图解析器的配置-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp" />
    </bean>

    <!-- 配置拦截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- 指定需要拦截的URL路径 -->
            <mvc:mapping path="/role/**"/>
            <mvc:mapping path="/admin/**"/>
            <!-- 定义拦截器的Bean -->
            <bean class="com.ssm.netctoss.interceptor.RoleInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

(2)前端nav.jsp设置分权限页面:

<div id="navi">
    <ul id="menu">
        <li><a href="${pageContext.request.contextPath}/index" class="index_off"></a></li>

        <!-- 仅管理员可见的导航项 -->
        <c:if test="${sessionScope.loggedInUser.adminCode == 'admin'}">
            <li><a href="${pageContext.request.contextPath}/role/list" class="role_off"></a></li>
            <li><a href="${pageContext.request.contextPath}/admin/list" class="admin_off"></a></li>
        </c:if>

        <!-- 其他所有用户可见的导航项 -->
        <li><a href="${pageContext.request.contextPath}/fee/list" class="fee_off"></a></li>
        <li><a href="${pageContext.request.contextPath}/account/list" class="account_off"></a></li>
        <li><a href="${pageContext.request.contextPath}/service/searchService" class="service_off"></a></li>
        <li><a href="${pageContext.request.contextPath}/bill/list" class="bill_off"></a></li>
        <li><a href="${pageContext.request.contextPath}/report/list" class="report_off"></a></li>

        <!-- 信息和密码修改仅管理员可见 -->
        <c:if test="${sessionScope.loggedInUser.adminCode == 'admin'}">
            <li>
                <a href="${pageContext.request.contextPath}/user/info?adminId=${sessionScope.loggedInUser.adminId}" class="information_off"></a>
            </li>
        </c:if>
        <li><a href="${pageContext.request.contextPath}/user/modipwd" class="password_off"></a></li>
    </ul>
</div>

(3)创建拦截器实现角色权限访问:

建立interceptor包,包下创建RoleInterceptor.java拦截器,实现了根据登录身份进行权限检查从而实现控制访问不同登录角色的界面。仅举例权限验证代码:

package com.ssm.netctoss.interceptor;

import com.ssm.netctoss.pojo.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class RoleInterceptor implements HandlerInterceptor {

    /**
     * 在请求处理之前进行调用(Controller 方法调用之前)
     * @return true 表示继续流程(如调用下一个拦截器或处理器)
     *         false 表示中断流程(如重定向)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求的URL
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();

        // 定义需要管理员权限的URL路径
        if (uri.startsWith(contextPath + "/role/") || uri.startsWith(contextPath + "/admin/")) {
            // 获取用户信息
            User user = (User) request.getSession().getAttribute("loggedInUser");
            if (user == null) {
                // 用户未登录,重定向到 nopower.jsp
                response.sendRedirect(contextPath + "/nopower");
                return false;
            }
            if (!"admin".equals(user.getAdminCode())) {
                // 无管理员权限,重定向到 nopower.jsp
                response.sendRedirect(contextPath + "/nopower");
                return false;
            }
        }

        // 其他请求或有权限,继续处理请求
        return true;
    }

    /**
     * 在 Controller 方法调用之后,视图渲染之前调用
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        // 添加日志记录
    }

    /**
     * 在整个请求结束之后调用(即视图已经渲染完成)
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        // 资源清理
    }
}

解释:
  1. preHandle方法是HandlerInterceptor接口中的一个方法,在请求处理之前进行调用(Controller 方法调用之前)。该方法的返回值决定了请求是否继续执行。如果返回true,则表示继续流程(如调用下一个拦截器或处理器);如果返回false,则表示中断流程(如重定向)。

  2. 首先,通过request.getRequestURI()request.getContextPath()获取请求的URL和项目路径。然后,定义需要管理员权限的URL路径,如以/role//admin/开头的路径。

  3. 通过request.getSession().getAttribute("loggedInUser")获取用户信息。如果用户未登录,则重定向到“nopower.jsp”页面。如果用户登录了,但不是管理员,则同样重定向到“nopower.jsp”页面。

  4. 如果用户具有管理员权限,或者请求的URL不需要管理员权限,则继续处理请求。

(4)创建权限访问类:CommonController,确保 nopower.jsp 的映射

package com.ssm.netctoss.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class CommonController {

    @RequestMapping("/nopower")
    public String noPower() {
        return "nopower"; // 确保与 nopower.jsp 的视图名匹配
    }
}

(5) LoginController中,储存登录信息 sesson

// 登录成功,获取用户信息

User user = userService.getUserByAdminCode(adminCode);

// 将用户信息存储在会话中

session.setAttribute("loggedInUser", user);

(6)RoleController.java添加权限判断:

package com.ssm.netctoss.controller;

import com.ssm.netctoss.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpSession;

@Controller
@RequestMapping("/role")
public class RoleController {

    @RequestMapping("/list")
    public ModelAndView listRoles(HttpSession session) {
        User user = (User) session.getAttribute("loggedInUser");
        ModelAndView mav = new ModelAndView();
        
        if (user == null || !"admin".equals(user.getAdminCode())) {
            mav.setViewName("redirect:/nopower");
            return mav;
        }
        
        // 添加获取角色列表的逻辑
        // List<Role> roles = roleService.getAllRoles();
        // mav.addObject("roles", roles);
        mav.setViewName("roleList"); // 确保存在 roleList.jsp
        return mav;
    }
}

(7)AdminController.java添加权限判断:

package com.ssm.netctoss.controller;

import com.ssm.netctoss.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpSession;

@Controller
@RequestMapping("/admin")
public class AdminController {

    @RequestMapping("/list")
    public ModelAndView listAdmins(HttpSession session) {
        User user = (User) session.getAttribute("loggedInUser");
        ModelAndView mav = new ModelAndView();
        
        if (user == null || !"admin".equals(user.getAdminCode())) {
            mav.setViewName("redirect:/nopower");
            return mav;
        }
        
        // 添加获取管理员列表的逻辑
        // List<Admin> admins = adminService.getAllAdmins();
        // mav.addObject("admins", admins);
        mav.setViewName("adminList"); // 确保存在 adminList.jsp
        return mav;
    }
}

3、增加动态验证码(CAPTCHA)并验证功能。

(1)修改 LoginController 控制器添加验证码逻辑:

添加验证码生成方法,并验证。完整LoginController.java代码如下:

package com.ssm.netctoss.controller;

import com.ssm.netctoss.pojo.User;
import com.ssm.netctoss.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

// 导入必要的类
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.Random;

@Controller
public class LoginController {

    @Autowired
    private UserService userService;

    // 映射到/toLogin的GET请求,显示登录页面并生成验证码
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "login"; // 确保返回的视图名与您的视图解析器配置匹配
    }

    // 映射到/captcha的GET请求,生成并返回验证码图片
    @RequestMapping("/captcha")
    public void getCaptcha(HttpSession session, HttpServletResponse response) throws Exception {
        // 创建验证码图片
        int width = 80;
        int height = 30;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();
        Random random = new Random();

        // 填充背景色
        g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
        g.fillRect(0, 0, width, height);

        // 生成验证码字符串
        String captcha = getRandomNumber(4);

        // 绘制验证码字符串
        g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
        g.setFont(new Font("Times New Roman", Font.BOLD, 24));
        g.drawString(captcha, 15, 22);

        // 保存验证码到 session 中
        session.setAttribute("captcha", captcha);

        // 生成干扰线
        for (int i = 0; i < 4; i++) {
            g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
            g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height));
        }

        g.dispose();

        // 设置响应的类型为图片
        response.setContentType("image/jpeg");
        OutputStream os = response.getOutputStream();
        ImageIO.write(image, "jpeg", os);
        os.close();
    }

    // 获取随机验证码
    private String getRandomNumber(int num) {
        char[] chars = {
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
        };
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < num; i++) {
            int index = random.nextInt(chars.length);
            sb.append(chars[index]);
        }
        return sb.toString();
    }

    // 映射到/loginner的POST请求,处理登录表单提交
    @RequestMapping("/loginner")
    public ModelAndView login(
            ModelAndView modelAndView,
            @RequestParam String adminCode,
            @RequestParam String password,
            @RequestParam String captcha, // 获取用户输入的验证码
            HttpSession session) {

        // 从 session 中获取生成的验证码
        String sessionCaptcha = (String) session.getAttribute("captcha");

        // 验证验证码
        if (sessionCaptcha == null || !sessionCaptcha.equalsIgnoreCase(captcha)) {
            // 验证码错误
            modelAndView.addObject("errorLogin", "验证码错误,请重新输入");
            modelAndView.setViewName("login");
            return modelAndView;
        }

        // 验证用户名和密码
        if (userService.validateUser(adminCode, password)) {

            // 登录成功,获取用户信息
            User user = userService.getUserByAdminCode(adminCode);

            // 将用户信息存储在会话中(可选)
            session.setAttribute("loggedInUser", user);

            // 登录成功,重定向到主页面
            modelAndView.setViewName("redirect:index");
        } else {
            // 登录失败,返回登录页面并显示错误信息
            modelAndView.addObject("errorLogin", "用户名或密码错误,请重试");
            modelAndView.setViewName("login");
        }

        return modelAndView;
    }
}

(2)更新 login.jsp 前端页面:

更新 login.jsp 以显示动态生成的验证码图片,并确保用户可以刷新验证码。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>电信资费管理系统</title>
    <link type="text/css" rel="stylesheet" media="all" href="statics/styles/global.css" />
    <link type="text/css" rel="stylesheet" media="all" href="statics/styles/global_color.css" />
    <style>
        /* 添加验证码图片样式 */
        .captcha-img {
            cursor: pointer;
        }
    </style>
    <script>
        // JavaScript函数,用于点击验证码图片时刷新
        function refreshCaptcha() {
            var captchaImg = document.getElementById("captchaImage");
            // 添加一个随机参数以防止浏览器缓存
            captchaImg.src = "captcha?" + Math.random();
        }
    </script>
</head>
<body class="index">
<div class="login_box">
    <form action="loginner" method="post">
        <table>
            <tr>
                <td class="login_info">账号:</td>
                <td colspan="2"><input name="adminCode" type="text" class="width150" required /></td>
                <td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
            </tr>
            <tr>
                <td class="login_info">密码:</td>
                <td colspan="2"><input name="password" type="password" class="width150" required /></td>
                <td><span class="required">30长度的字母、数字和下划线</span></td>
            </tr>
            <tr>
                <td class="login_info">验证码:</td>
                <td class="width70"><input name="captcha" type="text" class="width70" required /></td>
                <td>
                    <!-- 显示动态生成的验证码图片,并添加点击事件以刷新 -->
                    <img src="captcha" alt="验证码" id="captchaImage" class="captcha-img" οnclick="refreshCaptcha()" title="点击更换" />
                </td>
            </tr>
            <tr>
                <td></td>
                <td class="login_button" colspan="2">
                    <input type="submit" value="登录" class="btn_save" style="border: 1px solid orange"/>
                </td>
                <td>
                    <!-- 显示登录错误信息 -->
                    <span class="required">
                        <c:if test="${not empty errorLogin}">
                            ${errorLogin}
                        </c:if>
                    </span>
                </td>
            </tr>
        </table>
    </form>
</div>
</body>
</html>

4、增加页面表单验证功能。

(1)资费修改页面举例: fee_modi.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>电信资费管理系统</title>
    <link type="text/css" rel="stylesheet" media="all" href="../statics/styles/global.css" />
    <link type="text/css" rel="stylesheet" media="all" href="../statics/styles/global_color.css" />
    <script language="javascript" type="text/javascript">
        //保存结果的提示
        function showResult() {
            showResultDiv(true);
            window.setTimeout(function() {
                showResultDiv(false);
            }, 3000);
        }

        function showResultDiv(flag) {
            var divResult = document.getElementById("save_result_info");
            if (flag)
                divResult.style.display = "block";
            else
                divResult.style.display = "none";
        }

        //切换资费类型
        function feeTypeChange(type) {
            var inputArray = document.getElementById("main").getElementsByTagName("input");
            if (type == 1) {
                inputArray[4].readOnly = true;
                inputArray[4].value = "";
                inputArray[4].className += " readonly";
                inputArray[5].readOnly = false;
                inputArray[5].className = "width100";
                inputArray[6].readOnly = true;
                inputArray[6].className += " readonly";
                inputArray[6].value = "";
            }
            else if (type == 2) {
                inputArray[4].readOnly = false;
                inputArray[4].className = "width100";
                inputArray[5].readOnly = false;
                inputArray[5].className = "width100";
                inputArray[6].readOnly = false;
                inputArray[6].className = "width100";
            }
            else if (type == 3) {
                inputArray[4].readOnly = true;
                inputArray[4].value = "";
                inputArray[4].className += " readonly";
                inputArray[5].readOnly = true;
                inputArray[5].value = "";
                inputArray[5].className += " readonly";
                inputArray[6].readOnly = false;
                inputArray[6].className = "width100";
            }
        }

        //表单验证
        function validateForm(formId) {
            const form = document.getElementById(formId);
            const name = form.querySelector('input[name="name"]');
            const baseDuration = form.querySelector('input[name="baseDuration"]');
            const baseCost = form.querySelector('input[name="baseCost"]');
            const unitCost = form.querySelector('input[name="unitCost"]');
            const descr = form.querySelector('textarea[name="descr"]');
            const privileges = form.querySelectorAll('input[name="privileges"]:checked');

            let isValid = true;
            let errorMessage = '';

            // 验证资费名称
            if (name && (name.value.length === 0 || name.value.length > 50)) {
                errorMessage += '资费名称不能为空且必须在50字符以内。\n';
                isValid = false;
            }

            // 验证基本时长
            if (baseDuration && (baseDuration.value < 1 || baseDuration.value > 600)) {
                errorMessage += '基本时长必须在1到600之间。\n';
                isValid = false;
            }

            // 验证基本费用和单位费用
            if (baseCost && (baseCost.value < 0 || baseCost.value > 99999.99)) {
                errorMessage += '基本费用必须在0到99999.99之间。\n';
                isValid = false;
            }
            if (unitCost && (unitCost.value < 0 || unitCost.value > 99999.99)) {
                errorMessage += '单位费用必须在0到99999.99之间。\n';
                isValid = false;
            }

            // 验证描述
            if (descr && descr.value.length > 100) {
                errorMessage += '资费说明长度不能超过100个字符。\n';
                isValid = false;
            }

            // 验证权限(仅在角色管理页面需要)
            if (privileges && privileges.length === 0) {
                errorMessage += '请至少选择一个权限。\n';
                isValid = false;
            }

            // 如果有错误,显示错误信息并阻止提交
            if (!isValid) {
                alert(errorMessage);
            }

            return isValid;
        }
    </script>
</head>
<body>
<!--Logo区域开始-->
<div id="header">
    <img src="../statics/images/logo.png" alt="logo" class="left"/>
    <a href="${pageContext.request.contextPath}/toLogin">[退出]</a>
</div>
<!--Logo区域结束-->
<!-- 导航区域开始 -->
<jsp:include page="../nav.jsp" />
<!-- 导航区域结束 -->
<!--主要区域开始-->
<div id="main">

    <form action="saveModify" method="POST" class="main_form" οnsubmit="return validateForm('formId');">
        <input type="hidden" name="costId" value="${cost.costId}" />
        <div class="text_info clearfix"><span>资费名称:</span></div>
        <div class="input_info">
            <input type="text" name="name" class="width300" value="${cost.name}" required />
            <span class="required">*</span>
            <div class="validate_msg_short">50长度的字母、数字、汉字和下划线的组合</div>
        </div>
        <div class="text_info clearfix"><span>资费类型:</span></div>
        <div class="input_info fee_type">
            <input type="radio" name="costType" value="1" id="monthly" οnclick="feeTypeChange(1);" <c:if test="${cost.costType == '1'}">checked</c:if> />
            <label for="monthly">包月</label>
            <input type="radio" name="costType" value="2" id="package" οnclick="feeTypeChange(2);" <c:if test="${cost.costType == '2'}">checked</c:if> />
            <label for="package">套餐</label>
            <input type="radio" name="costType" value="3" id="timeBased" οnclick="feeTypeChange(3);" <c:if test="${cost.costType == '3'}">checked</c:if> />
            <label for="timeBased">计时</label>
        </div>
        <div class="text_info clearfix"><span>基本时长:</span></div>
        <div class="input_info">
            <input type="number" name="baseDuration" min="1" max="600" class="width100" value="${cost.baseDuration}" required />
            <span class="info">小时</span>
            <span class="required">*</span>
            <div class="validate_msg_long">1-600之间的整数</div>
        </div>
        <div class="text_info clearfix"><span>基本费用:</span></div>
        <div class="input_info">
            <input type="number" step="0.01" name="baseCost" class="width100" value="${cost.baseCost}" required />
            <span class="info">元</span>
            <span class="required">*</span>
            <div class="validate_msg_long error_msg">0-99999.99之间的数值</div>
        </div>
        <div class="text_info clearfix"><span>单位费用:</span></div>
        <div class="input_info">
            <input type="number" step="0.01" name="unitCost" class="width100" value="${cost.unitCost}" required />
            <span class="info">元/小时</span>
            <span class="required">*</span>
            <div class="validate_msg_long error_msg">0-99999.99之间的数值</div>
        </div>
        <div class="text_info clearfix"><span>资费说明:</span></div>
        <div class="input_info_high">
            <textarea name="descr" class="width300 height70">${cost.descr}</textarea>
            <div class="validate_msg_short error_msg">100长度的字母、数字、汉字和下划线的组合</div>
        </div>
        <div class="button_info clearfix">
            <input type="submit" value="保存" class="btn_save" οnclick="showResult();" />
            <input type="button" value="取消" class="btn_save" οnclick="window.location.href='list';" />
        </div>
        <!-- 保存结果的提示 -->
        <div id="save_result_info" class="save_fail" style="display:none;">保存成功!</div>
    </form>
</div>
<!--主要区域结束-->
<div id="footer">
    <span>[Copyright © 1996-2024 NET Corporation, All Rights Reserved]</span>
    <br />
    <span>版权所有(C)  Company  NET</span>
</div>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值