当前位置: 首页 > news >正文

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>

相关文章:

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

一、新增的模块&#xff1a; 在原项目基础上&#xff0c;新增加了以下功能&#xff1a; 1、增加AspectJ 框架的AOP 异常记录和事务管理模块。 2、增加SpringMVC的拦截器&#xff0c;实现登录 控制页面访问权限。 3、增加 Logback日志框架&#xff0c;记录日志。 4、增加动态验…...

vue2项目的路由使用history模式,刷新会导致页面404的问题

在vue2项目中&#xff0c;如果我们使用的路由是history模式&#xff0c;刷新会导致页面404&#xff0c;解决方法很简单&#xff0c;在vue.config.js文件中的devServer下增加historyApiFallback: true; 代码如下: module.exports {devServer: {historyApiFallback: true,} }...

pytest框架之fixture测试夹具详解

前言 大家下午好呀&#xff0c;今天呢来和大家唠唠pytest中的fixtures夹具的详解&#xff0c;废话就不多说了咱们直接进入主题哈。 一、fixture的优势 ​ pytest框架的fixture测试夹具就相当于unittest框架的setup、teardown&#xff0c;但相对之下它的功能更加强大和灵活。 …...

【浏览器】如何正确使用Microsoft Edge

1、清理主页广告 如今的Microsoft Edge 浏览器 主页太乱了&#xff0c;各种广告推送&#xff0c;点右上角⚙️设置&#xff0c;把快速链接、网站导航、信息提要、背景等全部关闭。这样你就能得到一个超级清爽的主页。 网站导航       关闭 …...

打印1000年到2000年之间的闰年

我们要打印1000年到2000年之间的闰年&#xff0c;首先我们先输出1000年到2000年之间的所有的年份&#xff0c;同时我们将闰年的判断方法输入到其中 闰年需要满足下列两个条件的其中之一&#xff1a; 1.能被4整除但不能被100整除 2.能被400整除 打印1000年到2000年之间的闰年…...

nn.Identity()

在 PyTorch 中&#xff0c;nn.Identity()是一个简单的模块&#xff0c;它的作用是在模型中作为一个占位符或者不进行任何操作的层&#xff0c;直接返回输入。 一、使用方法 以下是一个简单的使用示例&#xff1a; import torch import torch.nn as nn# 创建一个 Identity 层…...

Java 快速排序

快速排序&#xff08;Quicksort&#xff09;是一种高效的排序算法&#xff0c;采用分治法&#xff08;Divide and Conquer&#xff09;的策略来把一个序列分为较小和较大的两个子序列&#xff0c;然后递归地排序两个子序列。以下是用Java实现的快速排序算法&#xff1a; publi…...

51单片机的智能衣柜【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块光照传感器时钟模块温湿度传感器继电器按键、LED等模块构成。适用于智能衣柜、智能衣橱、紫外线定时消毒等相似项目。 可实现功能: 1、LCD1602实时显示北京时间、温湿度和开关门状态 2、时钟模块DS1302采集时间 …...

SAP_FI_表ACDOCA取代的表

在 SAP S/4HANA 系统中&#xff0c;ACDOCA&#xff08;通用分录表&#xff0c;Universal Journal&#xff09;引入了全新的数据结构&#xff0c;取代了原先 ERP 系统中多个财务和控制模块的表。ACDOCA 通过一个单一表格整合了财务会计&#xff08;FI&#xff09;和管理会计&…...

论文《OneLLM:One Framework to Align All Modalities with Language》

&#xff08;没有会员只有做100个节点&#xff0c;mindmaster金主爸爸可不可以给我一个会员啊啊啊啊呜呜呜~&#xff09; 欣赏论文的图和表&#xff1a; 表中作者将自己的模型那一行选择灰色作为背景&#xff0c;更加凸显自己的数据&#xff0c;另外对于最好的结果用加粗黑体…...

Ubuntu 22.04.4 LTS更换下载源

方法1&#xff1a;使用图形界面更换下载源 1. 打开软件和更新应用 2. 在Ubuntu 软件标签中&#xff0c;点击“下载自”旁边的下拉菜单&#xff0c;选择“其他” 3. 点击“选择最佳服务器”来自动选择最快的服务器 4. 选择服务器 5. 确定并关闭窗口&#xff0c;系统会提示您重新…...

html嵌入百度地图

html嵌入百度地图 key地址 https://lbsyun.baidu.com/apiconsole/key#/home &#xff0c;点进去注册应用、然后复制key换掉即可显示地图 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>百度地图搜索…...

【网络】详解TCP协议中的可靠传输

【网络】详解TCP协议中的可靠传输 一. TCP协议段格式二. 确认应答——确保可靠性的核心机制1.确保时序2.确保发送方知道数据是否被对方接收到 三. 超时重传1. 发送的数据丢包2. ACK报文丢失 一. TCP协议段格式 TCP协议段格式相比UDP要复杂很多&#xff0c;很多内容需要我们了解…...

【Python实例】Python读取并绘制nc数据

【Python实例】Python读取并绘制nc数据 准备&#xff1a;安装netCDF库等读取nc数据相关信息绘制图形利用basemap绘图 参考 准备&#xff1a;安装netCDF库等 以【1960-2020年中国1km分辨率月降水数据集】中2020年降水为例。 先在Panopoly中查看数据属性&#xff0c;如下&#…...

swift使用llama3.2-vision微调xray数据集

1.数据格式 [{"query": "通过这张胸部X光影像可以诊断出什么?","response": "根据X射线图像,心脏大小正常,肺部看起来很清晰。已经排除了肺炎、积液、水肿、气胸、腺病、结节或肿块的存在。该发现表明一切正常。换句话说,总体印象是胸…...

学习小课堂

1.多服务节点下Session-Cooki方案如何做&#xff1f; Session-Cookie 方案在单体环境是一个非常好的身份认证方案。但是&#xff0c;当服务器水平拓展成多节点时&#xff0c;Session-Cookie 方案就要面临挑战了。 举个例子&#xff1a;假如我们部署了两份相同的服务 A&#x…...

stm32学习笔记-RTC实时时钟

文章目录 一、RTC基础知识1.1 RTC简介1.2 RTC的晶振 二、stm32的RTC2.1 RTC和后备寄存器2.2 stm32 RTC结构框图及特性 三、stm32 RTC编程2.1 RTC初始化2.2 RTC控制程序 一、RTC基础知识 1.1 RTC简介 实时时钟的缩写是RTC(Real_Time Clock)。RTC 是集成电路&#xff0c;通常称…...

简历中的期望薪资怎么定?

在简历中撰写期望薪资时&#xff0c;既要体现你的价值认知&#xff0c;又要保持一定的灵活性和开放性&#xff0c;以便在后续的面试和薪资谈判中留有余地。以下是一些撰写期望薪资的合理方法&#xff1a; 一、明确薪资范围 1.市场调研&#xff1a; 在撰写期望薪资前&#xf…...

MySQL 中的 GROUP BY 使用

MySQL 中的 GROUP BY 使用指南 GROUP BY 是 SQL 中一个非常强大的语句&#xff0c;用于将查询结果按指定的列进行分组&#xff0c;并对每个分组执行聚合函数。它常常与聚合函数&#xff08;如 COUNT、SUM、AVG、MIN 和 MAX&#xff09;结合使用&#xff0c;以生成汇总信息。 …...

在 ubantu 20.04 云服务器上基于 bochs 编译 linux0.11

安装 bochs 将下面的命令全部执行一遍&#xff1a; sudo apt-get install build-essential sudo apt-get install xorg-dev sudo apt-get install bison sudo apt-get install g 我们区官网下载一下bochs的源码&#xff1a;bochs下载 这里我下载好了bochs2.6.8 这个版本的…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral&#xff08;热门工具 Ruff 的开发者&#xff09;推出的下一代高性能 Python 包管理器和构建工具&#xff0c;用 Rust 编写。它旨在解决传统工具&#xff08;如 pip、virtualenv、pip-tools&#xff09;的性能瓶颈&#xff0c;同时…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解

进来是需要留言的&#xff0c;先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码&#xff0c;输入的<>当成字符串处理回显到页面中&#xff0c;看来只是把用户输…...

Pydantic + Function Calling的结合

1、Pydantic Pydantic 是一个 Python 库&#xff0c;用于数据验证和设置管理&#xff0c;通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发&#xff08;如 FastAPI&#xff09;、配置管理和数据解析&#xff0c;核心功能包括&#xff1a; 数据验证&#xff1a;通过…...

ArcGIS Pro+ArcGIS给你的地图加上北回归线!

今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等&#xff0c;设置经线、纬线都以10间隔显示。 2、需要插入背会归线&#xf…...