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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttps://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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttps://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 表示中断流程(如重定向)*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取请求的URLString 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.jspresponse.sendRedirect(contextPath + "/nopower");return false;}if (!"admin".equals(user.getAdminCode())) {// 无管理员权限,重定向到 nopower.jspresponse.sendRedirect(contextPath + "/nopower");return false;}}// 其他请求或有权限,继续处理请求return true;}/*** 在 Controller 方法调用之后,视图渲染之前调用*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {// 添加日志记录}/*** 在整个请求结束之后调用(即视图已经渲染完成)*/@Overridepublic 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 {@Autowiredprivate 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";elsedivResult.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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/55998.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hunuan-DiT代码阅读

一 整体架构 该模型是以SD为基础的文生图模型&#xff0c;具体扩散模型原理参考https://zhouyifan.net/2023/07/07/20230330-diffusion-model/&#xff0c;代码地址https://github.com/Tencent/HunyuanDiT&#xff0c;这里介绍 Full-parameter Training 二 输入数据处理 这里…

netdata保姆级面板介绍

netdata保姆级面板介绍 基本介绍部署流程下载安装指令选择设置KSM为什么要启用 KSM&#xff1f;如何启用 KSM&#xff1f;验证 KSM 是否启用注意事项 检查端口启动状态 netdata和grafana的区别NetdataGrafananetdata各指标介绍总览system overview栏仪表盘1. CPU2. Load3. Disk…

3.使用条件语句编写存储过程(3/10)

引言 在现代数据库管理系统中&#xff0c;存储过程扮演着至关重要的角色。它们是一组为了执行特定任务而编写的SQL语句&#xff0c;这些语句被保存在数据库中&#xff0c;可以被重复调用。存储过程不仅可以提高数据库操作的效率&#xff0c;还可以增强数据的安全性和一致性。此…

RPA技术的定义与原理

RPA&#xff08;Robotic Process Automation&#xff09;即机器人流程自动化&#xff0c;是一种利用软件机器人或机器人工具来自动执行重复性、规则性和可预测性的业务流程的技术。以下是对RPA技术的详细介绍&#xff1a; 一、RPA技术的定义与原理 RPA技术通过模拟人工操作&a…

【redis-06】redis的stream流实现消息中间件

redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756【三】redis缓存穿透、缓存击穿、缓存雪崩htt…

关于Linux查看系统及版本信息的命令lsb_release命令以及Centos7中将redis服务写入systemctl服务

一、关于Linux查看系统及版本信息的命令lsb_release命令 linux查看系统是centos还是ubuntu&#xff0c;之前一直使用uname -a以及cat /etc/issue。但在某个服务器上发些这些都不行。有一个更好用的命令&#xff1a;lsb_release -a。如执行时提示-bash: lsb_release: 未找到命令…

Vscode+Pycharm+Vue.js+WEUI+django火锅(三)理解Vue

新创建的Vue项目里面很多文件&#xff0c;对于新手&#xff0c;老老实实做一下了解。 1.框架逻辑 框架的逻辑都是相通的&#xff0c;花点时间理一下就清晰了。 2.文件目录及文件 创建好的vue项目下&#xff0c;主要的文件和文件夹要先认识一下&#xff0c;并与框架逻辑对应起…

计算机毕业设计 校内跑腿业务系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

dayu_widgets-简介

前言: 越来越多的人开始使用python来做GUI程序&#xff0c;市面上却很少有好的UI控件。即使有也是走的商业收费协议&#xff0c;不敢使用&#xff0c;一个不小心就收到法律传票。 一、原始开源项目: 偶然在GitHub上发现了这个博主的开源项目。https://github.com/phenom-films…

YOLO11改进|SPPF篇|引入YOLOv9提出的SPPELAN模块

目录 一、【SPPELAN】模块1.1【SPPELAN】模块介绍1.2【SPPELAN】核心代码 二、添加【SPPELAN】模块2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【SPPELAN】模块 1.1【SPPELAN】模块介绍 下图是【SPPELAN】的结构图&#xff0c;让我们…

OOOPS:零样本实现360度开放全景分割,已开源 | ECCV‘24

全景图像捕捉360的视场&#xff08;FoV&#xff09;&#xff0c;包含了对场景理解至关重要的全向空间信息。然而&#xff0c;获取足够的训练用密集标注全景图不仅成本高昂&#xff0c;而且在封闭词汇设置下训练模型时也受到应用限制。为了解决这个问题&#xff0c;论文定义了一…

环球资源网 海外 globalsource reese84 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我删…

Graph知识图谱融入向量数据库,带来RAG效果飞升

01. 前言 随着大型语言模型&#xff08;LLMs&#xff09;在各种应用中的广泛使用&#xff0c;如何提升其回答的准确性和相关性成为一个关键问题。检索增强生成&#xff08;RAG&#xff09;技术通过整合外部知识库&#xff0c;为LLMs提供了额外的背景信息&#xff0c;有效地改…

使用激光跟踪仪提升码垛机器人精度

标题1.背景 码垛机器人是一种用于工业自动化的机器人&#xff0c;专门设计用来将物品按照一定的顺序和结构堆叠起来&#xff0c;通常用于仓库、物流中心和生产线上&#xff0c;它们可以自动执行重复的、高强度的搬运和堆垛任务。 图1 码垛机器人 传统调整码垛机器人的方法&a…

【重学 MySQL】四十六、创建表的方式

【重学 MySQL】四十六、创建表的方式 使用CREATE TABLE语句创建表使用CREATE TABLE LIKE语句创建表使用CREATE TABLE AS SELECT语句创建表使用CREATE TABLE SELECT语句创建表并从另一个表中选取数据&#xff08;与CREATE TABLE AS SELECT类似&#xff09;使用CREATE TEMPORARY …

maven指定模块快速打包idea插件Quick Maven Package

问题背景描述 在实际开发项目中&#xff0c;我们的maven项目结构可能不是单一maven项目结构&#xff0c;项目一般会用parent方式将各个项目进行规范&#xff1b; 随着组件的数量增加&#xff0c;就会引入一个问题&#xff1a;我们只想打包某一个修改后的组件A时就变得很不方便…

企业数据安全防泄密要怎么做?七个措施杜绝泄密风险!

随着信息技术的快速发展&#xff0c;企业的核心数据已成为最具价值的资产之一。然而&#xff0c;数据泄露事件频发&#xff0c;不仅会给企业造成严重的经济损失&#xff0c;还会影响企业的声誉。因此&#xff0c;如何防止企业数据泄密已成为每个企业管理者关注的重点。以下是七…

利用特征点采样一致性改进icp算法点云配准方法

1、index、vector 2、kdtree和kdtreeflann 3、if kdtree.radiusSearch(。。。) > 0)

js拼接html代码在线工具

具体请前往&#xff1a;在线Html转Js--将Html代码转成javascript动态拼接代码并保持原有格式

年薪30W的Java程序员都要求熟悉JVM与性能调优!

一、JVM 内存区域划分 1.程序计数器&#xff08;线程私有&#xff09; 程序计数器&#xff08;Program Counter Register&#xff09;&#xff0c;也有称作为 PC 寄存器。保存的是程序当前执行的指令的地址&#xff08;也可以说保存下一条指令的所在存储单元的地址&#xff0…