精华内容
下载资源
问答
  • 如何讲解自己的设计方案
    千次阅读
    2021-02-12 11:19:23

    《Java Web应用开发》项目设计参考方案(学时:84)

    项目一 网上商城项目开发环境的搭建(学时:8)

    一、教学目标

    最终目标:学会Java Web 开发环境搭建,了解如何在集成开发环境中开发JSP、Servlet程序,能在Web服务器上进行运行测试。

    促成目标:

    1. 了解JSP、Servlet技术;

    2. 了解Java Web 开发模式;

    3. 了解Java Web服务器运行条件,掌握如何安装、配置Jdk、Tommcat和集成开。

    二、工作任务

    1. 任务1 Java Web 环境搭建

    2. 任务2 简单JSP、Servlet测试

    三、活动设计

    1.活动思路

    首先介绍动态网页技术及相关开发模式,并将相关技术进行比较,从而引出本门课的重点。接着指出建立Web服务器的条件,引出如何搭建Java Web运行环境;最后学习集成开发环境的安装与设置,并学习如何写JSP程序、Servlet程序,并掌握如何在客户端进行运行测试。

    2.活动组织

    序号

    活动项目

    具体实施

    课时

    备注

    1

    Java Web 环境搭建

    教学做一体化

    4

    2

    简单JSP、Servlet测试

    教学做一体化

    4

    3.活动评价

    评价内容:根据学生具体任务完成情况、课后作业等情况进行评价。

    评价标准:

    实践部分评价:能基本完成相关软件的安装、环境配置为及格;独立完成相关软件的安装、环境配置,基本能进行代码编写及测试为良好;独立完成相关软件的安装、环境配置,并能进行正确的代码编写及测试为优秀。

    模块一 Java Web 环境搭建(学时:4)

    ?一、教学目标

    最终目标:掌握浏览静态网页和动态网页的技能,掌握分析网页所采用的技术的技能,能独立完成JSP运行环境的安装及配置

    促成目标:

    1.了解静态网页中的静态的概念;

    2.了解动态网页中的动态的概念;

    3. 了解JSP、Servlet技术;

    4. 了解Java Web 开发模式;

    5.学会JDK、Tomcat的下载、安装及配置。

    二、工作任务

    1. JDK、Tomcat的安装及配置;

    2.浏览各种类型的网页,分析出网页采用的技术;

    三、活动设计

    1.活动内容

    通过浏览多种类型的网页、导出静态网站与动态网站的概念,继而引出对各种网页开发技术的分析,引入JSP动态网页开发技术、开发模式及运行环境;浏览电子商务网站,导出网上商城的项目;分析JSP的基本概念,导出Web服务器的概念,引出JSP常用的Web服务品Tomcat。构建JSP运行环境,为下一步开发工作做好准备。

    子任务一: JDK安装与配置

    子任务二: Tomcat的安装与配置,服务器运行测试。

    2.活动组织

    序号

    活动项目

    具体实施

    课时

    备注

    1

    JDK安装与配置

    多媒体讲解,启发式教学及项目与任务引导教学,学生进行操作。

    2

    2

    Tomcat服务器的安装与配置、运行测试

    多媒体讲解、学生分组进行操作

    2

    四、主要实践知识

    1. 独立完成各种页面采用技术的分析

    2. 掌握JDK 安装,JAVA_HOME、CLASSPATH、PATH环境变量的配置。

    3. 掌握Tomcat安装,TOMCAT_HOME、CLASSPAHT环境变量的配置。

    4. Tomcat服务器的启动,JAVA WEB服务器的布署。

    五、主要理论知识

    1. 静态网页与动态网页的概念;

    2.多种动态网页开发技术;

    3.JSP运行原理。

    六、思考与练习

    1. JSP的运行原理;

    2.动态网页与静态网页的概念及区别;

    3.常用的动态网页开发技术及比较。

    4.下载JDK、Tomcat并进行安装与配置。

    模块二 编写简单的JSP程序(学时:4)

    一、教学目标

    最终目标:掌握JSP集成开发工具的安装、配置与使用,能编写简单的JSP程序及Servlet程序,并会启动Tomcat服务器及运行jsp程序和Servlet程序。

    促成目标:

    1.了解JSP开发工具(MyEclipse);

    2.第一个JSP、Servlet程序的创建;

    3.掌握虚拟目录的配置方法;

    4.掌握运行动态网页程序的方法。

    二、工作任务

    1. MyEclipse开发工具的使用;

    2. 建立第一个Java Web项目。

    三、活动设计

    1.活动内容

    通过运行成熟的网上商城案例,引出如何建立动态Web网站,从而引出常用的JSP集成开发工具MyEclipse,演示其下载、安装、配置过程,并建立第一个Java Web 项目和编写第一个JSP程序、第一个Servlet程序。并进行相关布署和运行发布与测试。

    2.活动组织

    序号

    活动项目

    具体实施

    课时

    备注

    1

    MyEclipse开发工具的安装与使用

    教学做一体化教学

    2

    2

    建立第一个Java Web项目

    教学做一体化教学

    2

    四、主要实践知识

    1. MyEclipse开发工具的安装与使用

    2. 第一个

    更多相关内容
  • IBMS集成管理平台设计方案知识讲解.pdfIBMS集成管理平台设计方案知识讲解.pdfIBMS集成管理平台设计方案知识讲解.pdfIBMS集成管理平台设计方案知识讲解.pdfIBMS集成管理平台设计方案知识讲解.pdfIBMS集成管理平台设计...
  • 厨房设计方案讲解.pptx
  • Revit方案设计实战案例讲解
  • 下一代防火墙设计方案V2讲解.pdf
  • 停车场出入口系统设计方案讲解.doc
  • 建筑工程技术毕业设计方案详细讲解.doc
  • 超市物流配送施工设计方案及对策讲解.doc
  • 消防、暖通安装工程施工组织设计方案知识讲解.docx
  • 消防、暖通安装工程施工组织设计方案知识讲解.pdf
  • 快捷系列产品与投影视频设备设计方案与详细讲解.doc
  • 风电场信息安全等级保护设计方案和实施情况介绍讲解.ppt
  • 呼叫中心技术方案设计讲解.doc
  • 分享一则在实际工作中写的方案,《海外B2B网站加速及安全设计方案.docx》,内附精彩视频讲解
  • 临潼雁泊台项目会所工程土方开挖工程施工组织设计方案改doc详细讲解.doc
  • 全局性设计规划-设计方案讲解
  • 消防管道试压及安装方案设计p讲解.doc
  • 医院装修:综合医院设计方案及用材讲解 医院装修:综合医院设计方案及用材讲解
  • FNET慧锦综合布线系统方案设计及预算案例讲解.pptx
  • F-NET 综合布线系统方案设计及预算案例讲解.pptx
  • 这是我收集的一部分FPGA设计方案和剑桥大学Verilog语言讲义,希望对广大FPGA爱好者有所帮助。
  • 大数据可视化产品设计方案详解,对大数据产品架构及其在教育、医疗、交通、政府等各行业的解决方案进行了讲解
  • 【小学生良好行为习惯养成教育的设计研究方案】课题开题报告讲解.doc
  • 电磁组直立行车参考设计方案,卓晴老师直立控制讲解。。。
  • 历年电子设计大赛方案 题目 及讲解 值得一看
  • 网站设计方案PPT

    2011-11-27 18:44:40
    网站设计方案PPT,讲解设计思路,希望给大家带来帮助。
  • 投标书制作-及范本讲解.zip软件投标书模板软件服务类IT项目管理方案商务技术标文件范文 投标书制作-及范本讲解.zip软件投标书模板软件服务类IT项目管理方案商务技术标文件范文 投标书制作-及范本讲解.zip软件投标书...
  • 综合布线系统设计方案,对网络设计进行详细讲解,赶快来下吧
  • 高品质USB声卡/音频芯片SSS1700|SSS1700设计96 KHz 24位采样率USB耳机麦克风方案|SSS1700中文设计方案讲解 台湾鑫创在2021年新推的一款SSS1700,是一款高品质USB声卡/音频芯片,具有96 KHz 24位采样率和外部音频...

                                                                             高品质USB声卡/音频芯片SSS1700|SSS1700设计96 KHz 24位采样率USB耳机麦克风方案|SSS1700中文设计方案讲解

     

          台湾鑫创在2021年新推的一款SSS1700,是一款高品质USB声卡/音频芯片,具有96 KHz 24位采样率和外部音频编解码器(24位/96 KHz I2S输入和输出),并具有内置立体声16/24位ADC、立体声16/24位DAC、耳机驱动程序、五频段硬件EQ、,音频锁相环,USB时钟振荡器,和USB FS控制器加物理层。外部24C02~24C16 EEPROM连接为USB VID/PID/产品字符串、默认增益设置和其他定制需求提供了灵活性。

    SSS1700的参数特性:

    符合USB规范v2.0全速运行

     

    符合USB音频设备类规范v1.0

     

    支持44.1KHz/48KHz/96KHz、16位/24位采样率(EEPROM选件)

     

    嵌入式数字混音器,开机后默认混音器静音(操作系统控制)

    当设置单ADC时,两个DAC通道都与该单ADC数据混合

     

    当设置立体声ADC时,L-ch DAC与L-ch ADC数据混合,R-ch DAC与R-ch ADC数据混合

     

    电源模式设置的ROM选项(USB总线电源100mA:默认或500mA配置)

     

    默认支持16和24位,48KHz采样率的ADC和DAC

     

    嵌入式耳机驱动,高达16欧姆负载驱动

     

    ADC线路输入到DAC输出数据路径选项

     

    用于16/24位编解码器DAC/ADC(EEPROM选件)的嵌入式I2S接口(主/从模式)

     

    用于16/24位编解码器DAC/ADC(EEPROM选件)的嵌入式SPDIF输入输出接口

     

    嵌入式无晶体片上振荡器

     

    支持USB挂起/恢复模式

     

    用于USB接口的嵌入式USB收发器

    对于耳机功能,USB音频功能拓扑有2个输入端子,2个输出端子,(1个混音器单元),(1个选择器单元)和(3)功能单元(一些单元可以通过ROM代码选项启用)

     

    支持一个控制端点、一个同步输出端点、一个同步输入端点、一个中断输入端点(HID使用中断输入和控制输出)

     

    备用零带宽设置,用于在设备处于非活动状态时释放USB总线上的播放带宽

     

    音量增大、音量减小、播放静音、录制静音、下一首曲目、上一首曲目、停止和播放/暂停引脚,供用户直接控制

     

    音量增大、音量减小、播放静音、下一首曲目、上一首曲目、停止和播放/暂停支持

     

    两线串行总线(I2C总线)用于外部MCU控制整个EEPROM空间可以通过MCU访问

     

    用于主机控制同步的USB HID

     

    外部串行EEPROM(24C02~24C16)接口,用于客户特定的USB视频、PID、产品字符串、序列号、默认增益、默认EQ设置、播放/记录启用和其他选项

     

    EEPROM写功能通过HID或供应商的具体要求,为大规模生产提供方便

     

    预加载的VID、PID和产品字符串以及设置优先级的设计选项:i)外部EEPROM ii)嵌入式ROM

     

    客户特定请求和新的虚拟寄存器(10XX\u 10XX;其中XX可通过寄存器写回和读回进行设置,以进行验证)用于软件保护

     

    GPIO和MCU接口寄存器通过HID读/写

     

    真正的头戴式耳机放大器解决方案

     

    支持CTIA/OMTP自动切换TRRS音频插孔(EEPROM选件)

     

    支持AD键检测(EEPROM选项)

     

    支持RGB LED(EEPROM选件)

     

    支持HID键盘(EEPROM选件)

     

    支持IIC初始外部编解码器(EEPROM选项)

     

    嵌入式1.2V POR

     

    嵌入式5V至3.3V(250mA容量)和3.3V至1.2V调节器,用于单个外部5V电源

     

    用于音量控制的嵌入式旋转编码器接口(EEPROM选件)

     

    1.2V数字核心和音频PLL操作、3.3V USB PLL操作和ADC/DAC操作

     

    兼容Win XP、Win 7、Win 10、Mac OS、Linux OS和Android OS,无需额外驱动程序

    SSS1700的设计特性与电气参数:

    1.绝对最大额定值

      2.直流特性

    3. 交流特性

     

    耳机输出

    麦克风输入特性

    4.引脚描述

     

    48脚LQFP/QFN的引脚输出图

     36脚QFN引脚输出图

    引脚列表表

     

    播放、录制和功能选项

     

     

    插入路径选择功能选项

    5.方框图和说明

     

    USB模式下的线路默认设置

    自动增益控制(AGC)

     

    SSS1700具有AGC(自动增益控制)功能。它可以自动调整ADC的输出范围,使ADC的输出保持在一个稳定的范围内。AGC控制原理图如下,增益可调范围为-23dB~+40dB,每级1dB可调。

     

    AGC参数设置可在EEPROM中设置。控制特性包括时间稳定性、误差范围、主动方式、保持时间、调速等,这些参数需要单独设置。其运行图如下:

    AGC调谐的目标是在两条蓝线以内。如图前所示,信号低于蓝线间隔,则AGC放大器将信号调至蓝线范围。类似地,在图中,信号超过蓝色间隔,然后AGC将信号降低到蓝色范围。

     

     

    多功能按键(4键)

     

    SSS1700最多支持4个多功能键。通过EEPROM设置,每个多功能键最多可以有四种不同的按钮操作方式。四种不同的按钮操作是“短按”、“连续两次短按”、“短按和长按”和“长按”。

     

    每个多功能按键对应不同的控制方式,以满足不同的功能需求,从而达到精简按键数量要求的目的。设置示意图如下:

     

    按键输入可从以下位置设置:功能输出可分配给:

     

    LED 灯效功能

     

    SSS1700具有立体声音频波形梯度指示器功能。通过EEPROM设置,可提供多达六个指示信号(L/R之差,为每个通道共用三个指示信号)。指示信号可接LED作为音频输出梯度指示器。当音频信号为零时,LED可设置为呼吸灯,以增加产品多样性。

     

    以下是功能图:

     

    设置音频输出指示灯时,可根据所需的输出范围进行调整;每个指示灯信号可有16个电平进行建议的音频设置。

     

    五频段均衡器

     

    SSS1700在播放路径上构建了5个频段的均衡功能,提供给用户进行音效调整。五波段均衡器的这些频率分别固定在60Hz、300Hz、1.2KHz、3.6KHz和12KHz。可将每个频带的增益设置为+12dB~-∞dB,如下所示:

     

    用户可根据需要调节多种音效,调节后的音效存储在EEPROM中,可以用单个按钮在循环方式下改变不同的音效,同时还提供单个LED指示开关音效。由默认情况下,SSS1700内置了一个低音炮的声音设置,因此,在没有外部EEPROM的情况下,仍然有一个EQ声音转换供用户使用。预设低音(低音炮)声音设置如下:

    SSS1700详细设计 96 KHz 24位采样率USB耳机麦克风方案结构方框图如下所示:

     

     

     

     

    展开全文
  • java 项目日志管理设计方案

    万次阅读 多人点赞 2017-05-27 16:00:00
    java 项目日志管理设计方案 因项目需要记录整个系统的操作记录,考虑到系统操作日志的数据量,单表很容易达到瓶颈,导致查询效率低下,顾使用分表方案,减小数据库的负担,缩短查询时间。目前对于分表的解决方案有...

    java 项目日志管理设计方案

    因项目需要记录整个系统的操作记录,考虑到系统操作日志的数据量,单表很容易达到瓶颈,导致查询效率低下,顾使用分表方案,减小数据库的负担,缩短查询时间。目前对于分表的解决方案有很多,本博文主要讲解博主自行实现的日志管理的解决方案,如有遗漏或错误的请各位大佬多多包涵
    鉴于总是有人私信要demo,这里将以前搭的一个简易的项目贴出来:https://gitee.com/jiangliuhong/syslog.git


    1 创建日志表

    1.1 日志表Sql语句如下

    具体表设计随项目情况而变化

    表创建SQL语句

    CREATE TABLE `sys_user_log` (
      `log_id` varchar(32) NOT NULL COMMENT '日志表id,uuid',
      `user_id` varchar(32) DEFAULT NULL COMMENT '用户id,记录操作用户',
      `module_name` varchar(225) NOT NULL COMMENT '模块名称',
      `operate` varchar(225) NOT NULL COMMENT '操作名称',
      `time` datetime NOT NULL COMMENT '操作时间',
      `class_name` varchar(225) NOT NULL COMMENT '类名称',
      `method_name` varchar(225) NOT NULL COMMENT '方法名称',
      `params` longtext COMMENT '传入参数',
      `ip` varchar(225) NOT NULL COMMENT '操作ip',
      PRIMARY KEY (`log_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    

    1.2 日志表创建

    因考虑到项目情况,顾为每月创建一个日志表

    1.2.1 表创建方法

    创建service、dao方法

    DbMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.carfi.vrcp.dao.SysDbMapper">
        <update id="createUserLogTable" parameterType="java.lang.String">
        	create table #{name,jdbcType=VARCHAR} like sys_user_log
        </update>
    </mapper>
    
    

    DbMapper.java

    public interface SysDbMapper {
        /**
         *  创建数据表
         * @param name 表名
         */
        void createUserLogTable(String name);
    }
    

    DbService.java

    public interface SysDbService {
        /**
         * 创建数据表
         *
         * @param preName   名称前缀
         * @param isSufTime 是否显示 时间后缀
         */
        void createTable(String preName, Boolean isSufTime);
    }
    
    

    DbServiceImpl.java

    
    @Service("dbService")
    public class DbServiceimpl implements DbService {
    
        @Autowired
        private DbMapper dbMapper;
    
        @Override
        public void createTable(String preName, Boolean sufTime) {
            if (sufTime) {
                dbMapper.createTable(preName + "_" + getSufName());
            } else {
                dbMapper.createTable(preName);
            }
        }
        
        /**
         * 获取名称后缀<br>
         * 格式: 20170301
         *
         * @return
         */
        private String getSufName() {
            Date d = new Date();
            String s = DateUtil.date2String(d, false);
            return s.replace("-", "");
        }
    }
    
    

    1.2.2 创建定时任务(每月第一天凌晨执行)

    BaseQuartz.java

    
    /**
     * 报警定时任务接口
     * 
     */
    public interface BaseQuartz {
    
    	/**
    	 * 定时任务执行方法
    	 */
    	public void work();
    }
    
    

    DbQuartz.java

    
    public class DbQuartz implements BaseQuartz {
        
        private static final Logger log = LoggerFactory.getLogger(DbQuartz.class);
        /**
         * 默认表名
         */
        private static String tableName = "sys_user_log";
    
        @Override
        public void work() {
            try {
                //创建表
                DbService dbService = (SysDbService) SpringUtil.getBean("sysDbService");
                dbService.createTable(tableName, true);
                log.info("创建表" + tableName + "成功");
                //更新系统用户日志表表缓存
                SysCacheUtil.flushSysUserLogTableName();
            } catch (Exception e) {
                log.error("创建表" + tableName + "失败");
                work();
            }
        }
        
    }
    
    

    quartz配置文件:spring-quartz.xml

    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:cache="http://www.springframework.org/schema/cache"
    	xsi:schemaLocation="  
           http://www.springframework.org/schema/context  
           http://www.springframework.org/schema/context/spring-context.xsd  
           http://www.springframework.org/schema/beans  
           http://www.springframework.org/schema/beans/spring-beans.xsd  
           http://www.springframework.org/schema/tx  
           http://www.springframework.org/schema/tx/spring-tx.xsd  
           http://www.springframework.org/schema/aop  
           http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/cache  
           http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
         ">
         
         <!-- 工作的bean -->
    	<!-- 数据库定时任务 开始 -->
    	<bean id="dbQuartz" class="com.carfi.vrcp.quartz.DbQuartz" />
    	<bean id="dbQuartzJobDetail"
    		class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    		<property name="targetObject">
    			<ref bean="dbQuartz" />
    		</property>
    		<property name="targetMethod">
    			<value>work</value>
    		</property>
    	</bean>
    	<!-- 定义触发器 -->
    	<bean id="dbQuartzJobTrigger"
    		class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    		<property name="jobDetail">
    			<ref bean="dbQuartzJobDetail" />
    		</property>
    		<property name="cronExpression">
    			<!-- 每月第一天凌晨执行 -->
    			<value>0 0 0 1 * ?</value>
    		</property>
    	</bean>
    	
    	<!-- 启动触发器的配置开始 -->
    	<bean name="startQuertz" lazy-init="false" autowire="no"
    		class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    		<property name="triggers">
    			<list>
    				<ref bean="dbQuartzJobTrigger" />
    			</list>
    		</property>
    	</bean>
    	
    </beans>  
    
    

    到止为止日志表自动创建逻辑完成

    2 日志表数据插入查询

    因考虑查询速度,采用根据传入的时间进行联合查询,规定只能查询连续3个月的日志数据,即前台传入 20170301,20170525 则联合表sys_user_log20170301,sys_user_log20170401,sys_user_log20170501三表进行联合查询。

    2.1 日志相关类源代码

    主要代码包括,日志实体类,日志查询类,日志表相关的dao、service 类

    SysUserLog.java

    
    import java.io.Serializable;
    import java.util.Date;
    
    import com.fasterxml.jackson.annotation.JsonFormat;
    
    /**
     * 系统用户日志
     */
    public class SysUserLog implements Serializable {
    
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = 1L;
    
    	/**日志id*/
    	private String logId;
    	/**用户id*/
    	private String userId;
    	/**模块名称*/
    	private String moduleName;
    	/**操作*/
    	private String operate;
    	/**时间*/
    	private Date time;
    	/**类名*/
    	private String className;
    	/**方法名*/
    	private String methodName;
    	/**传入参数*/
    	private String params;
    	/**操作ip*/
    	private String ip;
    	
    	public String getLogId() {
    		return logId;
    	}
    	public void setLogId(String logId) {
    		this.logId = logId;
    	}
    	public String getUserId() {
    		return userId;
    	}
    	public void setUserId(String userId) {
    		this.userId = userId;
    	}
    	public String getModuleName() {
    		return moduleName;
    	}
    	public void setModuleName(String moduleName) {
    		this.moduleName = moduleName;
    	}
    	public String getOperate() {
    		return operate;
    	}
    	public void setOperate(String operate) {
    		this.operate = operate;
    	}
    	
    	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    	public Date getTime() {
    		return time;
    	}
    	public void setTime(Date time) {
    		this.time = time;
    	}
    	
    	public String getClassName() {
    		return className;
    	}
    	public void setClassName(String className) {
    		this.className = className;
    	}
    	public String getMethodName() {
    		return methodName;
    	}
    	public void setMethodName(String methodName) {
    		this.methodName = methodName;
    	}
    	public String getParams() {
    		return params;
    	}
    	public void setParams(String params) {
    		this.params = params;
    	}
    	public String getIp() {
    		return ip;
    	}
    	public void setIp(String ip) {
    		this.ip = ip;
    	}
    	//扩展字段
    	/**用户账号*/
    	private String username;
    	/**组织名称*/
    	private String organizationName;
    
    	public String getUsername() {
    		return username;
    	}
    	public void setUsername(String username) {
    		this.username = username;
    	}
    	public String getOrganizationName() {
    		return organizationName;
    	}
    	public void setOrganizationName(String organizationName) {
    		this.organizationName = organizationName;
    	}
    	
    

    SysUserLogMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.carfi.vrcp.dao.SysUserLogMapper" >
      <resultMap id="BaseResultMap" type="com.carfi.vrcp.pojo.SysUserLog" >
        <id column="log_id" property="logId" jdbcType="VARCHAR" />
        <result column="user_id" property="userId" jdbcType="VARCHAR" />
        <result column="module_name" property="moduleName" jdbcType="VARCHAR" />
        <result column="operate" property="operate" jdbcType="VARCHAR" />
        <result column="time" property="time" jdbcType="TIMESTAMP" />
        <result column="class_name" property="className" jdbcType="VARCHAR" />
        <result column="method_name" property="methodName" jdbcType="VARCHAR" />
        <result column="params" property="params" jdbcType="VARCHAR" />
        <result column="ip" property="ip" jdbcType="VARCHAR" />
      </resultMap>
      <resultMap type="com.carfi.vrcp.pojo.SysUserLog" id="BaseResultMapExt" extends="BaseResultMap">
      	<result column="username" property="username" jdbcType="VARCHAR" />
        <result column="full_name" property="organizationName" jdbcType="VARCHAR" />
      </resultMap>
      <sql id="BaseColumn">
      	log_id, user_id, module_name, operate, time, class_name, method_name, params, ip
      </sql>
      <insert id="insertToTable">
      	insert into ${tableName}
      	(log_id, user_id, module_name, operate, time, class_name, method_name, params, ip)
      	values
      	(
      	#{log.logId,jdbcType=VARCHAR},
      	#{log.userId,jdbcType=VARCHAR},
      	#{log.moduleName,jdbcType=VARCHAR},
      	#{log.operate,jdbcType=VARCHAR},
      	#{log.time,jdbcType=TIMESTAMP},
      	#{log.className,jdbcType=VARCHAR},
      	#{log.methodName,jdbcType=VARCHAR},
      	#{log.params,jdbcType=VARCHAR},
      	#{log.ip,jdbcType=VARCHAR}
      	)
      </insert>
      <insert id="insert" parameterType="com.carfi.vrcp.pojo.SysUserLog" >
      	insert into sys_user_log 
      	(log_id, user_id, module_name, operate, time, class_name, method_name, params, ip)
      	values
      	(
      	#{logId,jdbcType=VARCHAR},
      	#{userId,jdbcType=VARCHAR},
      	#{moduleName,jdbcType=VARCHAR},
      	#{operate,jdbcType=VARCHAR},
      	#{time,jdbcType=TIMESTAMP},
      	#{className,jdbcType=VARCHAR},
      	#{methodName,jdbcType=VARCHAR},
      	#{params,jdbcType=VARCHAR},
      	#{ip,jdbcType=VARCHAR}
      	)
      </insert>
      <!-- <select id="selectAll" resultMap="BaseResultMap">
      	select 
      	<include refid="BaseColumn"/>
      	from sys_user_log
      </select> -->
       <select id="selectAll" resultMap="BaseResultMapExt" parameterType="com.carfi.vrcp.query.SysUserLogQuery">
      	SELECT
    	U.USERNAME,
    	O.FULL_NAME,
    	UL.LOG_ID,
    	UL.USER_ID,
    	UL.MODULE_NAME,
    	UL.OPERATE,
    	UL.TIME,
    	UL.CLASS_NAME,
    	UL.METHOD_NAME,
    	UL.PARAMS,
    	UL.IP
    	FROM
    		V_USER_LOG UL
    	INNER JOIN SYS_USER U ON U.USER_ID = UL.USER_ID
    	INNER JOIN SYS_ORGANIZATION O ON O.ORGANIZATION_ID = U.ORGANIZATION_ID
    	<where>
    		<if test="organizationId != null and organizationId != ''">
    			AND O.ORGANIZATION_ID = #{organizationId,jdbcType=VARCHAR}
    		</if>
    		<if test="organizationIds != null">
    			AND O.ORGANIZATION_ID IN 
    			<foreach collection="organizationIds" item="oid" open="(" separator="," close=")">
    				#{oid,jdbcType=VARCHAR}
    			</foreach>
    		</if>
    		<if test="username != null and username != ''">
    			AND UL.USER_ID LIKE CONCAT('%',#{username,jdbcType=VARCHAR},'%')
    		</if>
    		<if test="moduleName != null and moduleName != ''">
    			AND UL.MODULE_NAME LIKE CONCAT('%',#{moduleName,jdbcType=VARCHAR},'%')
    		</if>
    	</where>
    	ORDER BY UL.TIME DESC 
      </select>
      <select id="selectAllByTables" resultMap="BaseResultMapExt">
      	SELECT
    	U.USERNAME,
    	O.FULL_NAME,
    	UL.LOG_ID,
    	UL.USER_ID,
    	UL.MODULE_NAME,
    	UL.OPERATE,
    	UL.TIME,
    	UL.CLASS_NAME,
    	UL.METHOD_NAME,
    	UL.PARAMS,
    	UL.IP
    	FROM
    		${table} UL
    	INNER JOIN SYS_USER U ON U.USER_ID = UL.USER_ID
    	INNER JOIN SYS_ORGANIZATION O ON O.ORGANIZATION_ID = U.ORGANIZATION_ID
    	<where>
    		<if test="query.organizationId != null and query.organizationId != ''">
    			AND O.ORGANIZATION_ID = #{query.organizationId,jdbcType=VARCHAR}
    		</if>
    		<if test="query.organizationIds != null">
    			AND O.ORGANIZATION_ID IN 
    			<foreach collection="query.organizationIds" item="oid" open="(" separator="," close=")">
    				#{oid,jdbcType=VARCHAR}
    			</foreach>
    		</if>
    		<if test="query.username != null and query.username != ''">
    			AND U.USERNAME LIKE CONCAT('%',#{query.username,jdbcType=VARCHAR},'%')
    		</if>
    		<if test="query.moduleName != null and query.moduleName != ''">
    			AND UL.MODULE_NAME LIKE CONCAT('%',#{query.moduleName,jdbcType=VARCHAR},'%')
    		</if>
    		<if test="query.startTime != null and query.endTime != null">
    			AND UL.TIME BETWEEN #{query.startTime,jdbcType=TIMESTAMP} AND #{query.endTime,jdbcType=TIMESTAMP}
    		</if>
    	</where>
    	ORDER BY UL.TIME DESC 
      </select>
    </mapper>
    
    

    SysUserLogMapper.java

    public interface SysUserLogMapper {
    
    	/**
    	 * 新增日志记录
    	 * @param sysUserLog
    	 * @return
    	 */
    	int insert(SysUserLog sysUserLog);
    	/**
    	 * 添加日志到指定数据库
    	 * @param tableName
    	 * @param sysUserLog
    	 * @return
    	 */
    	int insertToTable(@Param("tableName") String tableName,@Param("log") SysUserLog sysUserLog);
    	/**
    	 * 查询所有
    	 * @return
    	 */
    	List<SysUserLog> selectAll(SysUserLogQuery query);
    	
    	
    	List<SysUserLog> selectAllByTables(@Param("table") String table,@Param("query") SysUserLogQuery query);
    }
    

    SysUserLogService.java

    PageInfo 为Mybatis分页插件“PageHelp”中的分页辅助类

    public interface SysUserLogService {
    
        /**
         * 添加系统用户日志
         *
         * @param sysUserLog 日志信息
         */
        void addUserLog(SysUserLog sysUserLog);
    
        /**
         * 分页获取日志列表
         *
         * @param query  查询参数
         * @param start  页数
         * @param length 每页个数
         * @return
         */
        PageInfo queryPage(SysUserLogQuery query, Integer start, Integer length) throws Exception;
    

    SysUserLogServiceImpl.java

    SysCacheUtil:项目中集成了EhCahe缓存,而后根据项目的缓存规则封装的缓存工具类。在该日志查询、存储方案中将根据数据库中的日志表进行操作,顾将日志数据表名存入缓存。查询系统数据库中日志数据表表名的sql语句如下:
    SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE=‘BASE TABLE’ AND TABLE_SCHEMA = ‘数据库名称’ AND TABLE_NAME LIKE CONCAT (‘sys_user_log’,’%’);

    
    /**
     * 用户日志管理业务实现
     *
     * @author jiangliuhong
     * @CREATEDATE 2017年2月13日
     */
    @Service("sysUserLogService")
    public class SysUserLogServiceImpl implements SysUserLogService {
    
        @Autowired
        private SysUserLogMapper userLogMapper;
        @Autowired
        private DbService dbService;
    
        /**
         * sys_user_log
         */
        private static String preTableName = "sys_user_log";
    
        @Override
        public void addUserLog(SysUserLog sysUserLog) {
            try {
                userLogMapper.insertToTable(getUserLogTableName(), sysUserLog);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public PageInfo queryPage(SysUserLogQuery query, Integer start, Integer length) throws Exception {
            // 判断开始结束日期是否为空
            if (query.getStartTime() == null || query.getEndTime() == null) {
                throw new CustomerException("开始、结束时间不能为空");
            }
    		// 从缓存中读取系统数据库中的日志表表名
            List<String> userLogTableName = (List<String>) SysCacheUtil.getSysUserLogTableName();
            // 检查系统用户日志表名缓存是否存在,不存在则查询
            if (userLogTableName == null) {
                userLogTableName = dbService.queryUserLogTableName();
            }
            String logTable = "";
            if (userLogTableName == null ? true : userLogTableName.size() > 0) {
                // 标记是否比较开始时间
                Boolean isStart = true;
                for (int i = 0; i < userLogTableName.size(); i++) {
                    if (isStart) {
                        // 如果表创建时间与开始时间时间同年同月 则使循环体往下执行
                        if (DateUtil.equals(changeToDate(userLogTableName.get(i)), query.getStartTime())
                                || DateUtil.countSecond(changeToDate(userLogTableName.get(i)), query.getStartTime()) >= 0) {
                            logTable = " ( select * from " + preTableName;
                            isStart = false;
                        } else {
                            continue;
                        }
                    }
                    logTable = logTable + " union all " + " select * from " + userLogTableName.get(i);
                    // 如果表创建时间与结束时间同年同月 则跳出循环
                    if (DateUtil.equals(changeToDate(userLogTableName.get(i)), query.getEndTime())) {
                        break;
                    }
                }
                if (!isStart) {
                    logTable = logTable + " ) ";
                } else {
                    logTable = preTableName;
                }
            } else {
                logTable = preTableName;
            }
            // 分页计算
            start = start / length + 1;
            PageHelper.startPage(start, length);
            // List<SysUserLog> userLogs = userLogMapper.selectAll(query);
            List<SysUserLog> userLogs = userLogMapper.selectAllByTables(logTable, query);
            PageInfo pageInfo = new PageInfo(userLogs);
            return pageInfo;
        }
    
        /**
         * 获取最新的日志表表名
         *
         * @return
         */
        private String getUserLogTableName() {
            //取用户日志表名集合
            List<String> userLogTableName = SysCacheUtil.getSysUserLogTableName();
            // 判断最新表名是否与该月同月,不同月则创建该月日志表
            if (userLogTableName == null ? true : userLogTableName.size() <= 0) {
                dbService.createTable("sys_user_log", true);
                userLogTableName = SysCacheUtil.flushSysUserLogTableName();
            } else {
                // userLogTableName.size() > 0
                String tableName = userLogTableName.get(userLogTableName.size() - 1);
                if (!DateUtil.equals(changeToDate(tableName), new Date(System.currentTimeMillis()))) {
                    // 不同月,则创建该月表,并更新日志缓存
                    dbService.createTable("sys_user_log", true);
                    userLogTableName = SysCacheUtil.flushSysUserLogTableName();
                }
            }
            if (userLogTableName == null ? true : userLogTableName.size() <= 0) {
                return preTableName;
            }
    
            // 更新缓存
            SysCacheUtil.setSysUserLogTableName(userLogTableName);
            return userLogTableName.get(userLogTableName.size() - 1);
        }
    
        /**
         * 获取名称后缀<br>
         * 格式: 20170301
         *
         * @return
         */
        @SuppressWarnings("unused")
        private String getSufName() {
            Date d = new Date();
            String s = DateUtil.date2String(d, false);
            s = s.replace("-", "");
            s = s.substring(0, s.length() - 2);
            s = s + "01";
            return s;
        }
    
        /**
         * 计算日志表时间
         *
         * @param tabelName 表名
         * @return
         */
        private Date changeToDate(String tabelName) {
            int lastIndexOf = tabelName.lastIndexOf('_');
            if (lastIndexOf >= 0) {
                tabelName = tabelName.substring(lastIndexOf + 1);
                String strDate = tabelName.substring(0, 4) + "-" + tabelName.substring(4, 6) + "-"
                        + tabelName.substring(6, 8);
                return DateUtil.string2Date(strDate);
            } else {
                return null;
            }
        }
    }
    
    

    SysUserLogQuery.java

    该类为日志表辅助查询类,具体查询条件根据项目实际情况而定

    import java.util.Date;
    import java.util.List;
    
    /**
     * 日志查询类
     */
    public class SysUserLogQuery {
    
    	/**组织id*/
    	private String organizationId;
    	/**组织集合*/
    	private List<String> organizationIds;
    	/**开始时间*/
    	private Date startTime;
    	/**结束时间*/
    	private Date endTime;
    	/**用户名*/
    	private String username;
    	/**模块名称*/
    	private String moduleName;
    	public String getOrganizationId() {
    		return organizationId;
    	}
    	public void setOrganizationId(String organizationId) {
    		this.organizationId = organizationId;
    	}
    	public List<String> getOrganizationIds() {
    		return organizationIds;
    	}
    	public void setOrganizationIds(List<String> organizationIds) {
    		this.organizationIds = organizationIds;
    	}
    	public Date getStartTime() {
    		return startTime;
    	}
    	public void setStartTime(Date startTime) {
    		this.startTime = startTime;
    	}
    	public Date getEndTime() {
    		return endTime;
    	}
    	public void setEndTime(Date endTime) {
    		this.endTime = endTime;
    	}
    	public String getUsername() {
    		return username;
    	}
    	public void setUsername(String username) {
    		this.username = username;
    	}
    	public String getModuleName() {
    		return moduleName;
    	}
    	public void setModuleName(String moduleName) {
    		this.moduleName = moduleName;
    	}
    	
    }
    
    

    DateUtil.java

    该类为自行封装的时间处理工具类,代码如下:

    import java.sql.Timestamp;
    import java.text.DateFormat;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    
    public class DateUtil {
    
        public static Date string2Date(String time) {
            try {
                Date date = new Date();
                DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                if (time.length() <= 10) {
                    time = time + " 00:00:00";
                }
                date = sdf.parse(time);
                return date;
            } catch (ParseException e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * @param date 时间对象
         * @param hms  是否显示时分秒
         * @return
         */
        public static String date2String(Date date, Boolean hms) {
            DateFormat sdf;
            if (hms) {
                sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            } else {
                sdf = new SimpleDateFormat("yyyy-MM-dd");
            }
            String time = sdf.format(date);
            return time;
        }
    
        public static Timestamp string2Timestamp(String time) {
            Timestamp ts = new Timestamp(System.currentTimeMillis());
            if (time.length() <= 10) {
                time = time + " 00:00:00";
            }
            ts = Timestamp.valueOf(time);
            return ts;
        }
    
        /**
         * @param ts  时间戳对象
         * @param hms 是否显示时分秒
         * @return
         */
        public static String timestamp2String(Timestamp ts, Boolean hms) {
            DateFormat sdf;
            if (hms) {
                sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            } else {
                sdf = new SimpleDateFormat("yyyy-MM-dd");
            }
            String tsStr = "";
            tsStr = sdf.format(ts);
            return tsStr;
        }
    
        public static Date timestamp2Date(Timestamp ts) {
            Date date = new Date();
            date = ts;
            return date;
        }
    
        public static Timestamp date2Timestamp(Date date) {
            String time = "";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            time = sdf.format(date);
            Timestamp ts = Timestamp.valueOf(time);
            return ts;
        }
    
        /**
         * 计算两个时间之间的小时差<br>
         * start - stop
         *
         * @param start 开始时间
         * @param stop  结束时间
         * @return
         */
        public static Long countHour(Date start, Date stop) {
            long diff = start.getTime() - stop.getTime();
            long hour = diff / (60 * 60 * 1000);
            return hour;
        }
    
        /**
         * 计算两个时间之间的分钟数差 <br>
         * start - stop
         *
         * @param start 开始时间
         * @param stop  结束时间
         * @return
         */
        public static Long countMinute(Date start, Date stop) {
            long diff = start.getTime() - stop.getTime();
            long min = diff / (60 * 1000);
            return min;
        }
    
        /**
         * 计算两个时间之间的秒数差<br>
         * start - stop
         *
         * @param start 开始时间
         * @param stop  结束时间
         * @return
         */
        public static Long countSecond(Date start, Date stop) {
            long diff = start.getTime() - stop.getTime();
            long sec = diff / 1000;
            return sec;
        }
    
        /**
         * 按天增加或减时间
         *
         * @param date
         * @param days  增减的天数
         * @param hms   是否显示时分秒
         * @param isAdd 加减标识,false 是减,true是加
         * @return
         */
        public static String addOrMinusDate(Date date, int days, Boolean hms, Boolean isAdd) {
            long d = (long) days;
            SimpleDateFormat df = null;
            if (hms) {
                df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            } else {
                df = new SimpleDateFormat("yyyy-MM-dd");
            }
            if (!isAdd) {
                return df.format(new Date(date.getTime() - (d * 24 * 60 * 60 * 1000)));
            } else {
                return df.format(new Date(date.getTime() + (d * 24 * 60 * 60 * 1000)));
            }
        }
    
        /**
         * 判断两个日期是否同年同月
         *
         * @param date1 时间1
         * @param date2 时间2
         * @return
         */
        public static boolean equals(Date date1, Date date2) {
            Calendar calendar1 = Calendar.getInstance();
            calendar1.setTime(date1);
            Calendar calendar2 = Calendar.getInstance();
            calendar2.setTime(date2);
            return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR)
                    && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH);
        }
    
    

    2.2 用户日志的记录

    用户日志的记录,主要通过自定义java注解,通过在service方法标记注解,使用spring aop进行日志存储

    2.2.1 自定义java注解

    自定义注解主要包括模块名称、操作内容两个内容,其使用方式为:@LogAnnotation(moduleName = “角色管理”, operate = “新增角色”)
    如果需要其他内容,可根据以下源码进行扩展

    LogAnnotation.java

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 自定义日志注解
     */
    @Retention(RetentionPolicy.RUNTIME)  
    @Target({ElementType.METHOD})  
    @Documented
    public @interface LogAnnotation {
    	
    	/**模块名称*/
        String moduleName() default "";  
        /**操作内容*/
        String operate() default "";  
    	
    }
    

    注解使用:

    @LogAnnotation(moduleName = "角色管理", operate = "新增角色")
    @Override
    public void saveRole(SysRole role, String[] perIds) {
    

    2.2.2 切面配置

    aop配置

    	<aop:config>
    	    <aop:pointcut id="pc" expression="execution(* com.carfi.vrcp.service.*.*.*(..))"/> 
    	    <!--把事务控制在Service层-->
       <aop:advisor pointcut-ref="pc" advice-ref="txAdvice" />
      </aop:config>
      <bean id="logInterceptor" class="XXXX.XXXX.XXX.LogInterceptor"></bean>
      <aop:config>  
        <aop:aspect id="logAop" ref="logInterceptor" >
        	<aop:pointcut expression="execution(* com.carfi.vrcp.service.*.*.*(..))" id="logPoint"/>
        	<aop:after-returning method="afterReturning" pointcut-ref="logPoint" />
        </aop:aspect>
      </aop:config>
    

    LogInterceptor.java

    定义拦截器,重写afterReturning方法,在调用service方法完成之后触发该方法实现日志写入

    /**
     * 日志拦截器
     * 
     */
    public class LogInterceptor {
    	/**
    	 * 方法正常完成后执行方法
    	 * @param point
    	 */
    	@SuppressWarnings("rawtypes")
    	public void afterReturning(JoinPoint point) {
            try {
            	Subject subject = null ;
            	try {
    				subject = CarfiUserUtil.getSubject();
    			} catch (Exception e) {
    				e.printStackTrace();
    				//发生异常则不进行
    				return ;
    			}
            	SysUserLog userLog = new SysUserLog();
            	String targetName = point.getTarget().getClass().getName();
    			Class targetClass = Class.forName(targetName);
    			String methodName = point.getSignature().getName();
    			Method[] method = targetClass.getMethods();
    			Object[] params = point.getArgs(); // 获得参数列表
    			for (Method m : method) {
    				if (m.getName().equals(methodName)) {
    					Class[] tmpCs = m.getParameterTypes();
    					if (tmpCs.length == params.length) {
    						// 获取注解内容
    						LogAnnotation logAnnotation = m.getAnnotation(LogAnnotation.class);
    						if(logAnnotation != null){
    							//写入参数
    							if(params.length>0){
    								// 使用json转换工具 将参数转为json串,以便存入数据库
    								String jsonStr = JsonUtil.toJSONStr(params);
    								userLog.setParams(jsonStr);
    							}
    							//获取模块名称
    							String moduleName = logAnnotation.moduleName();
    							//获取操作名称
    							String operate = logAnnotation.operate();
    							userLog.setModuleName(moduleName);
    							userLog.setOperate(operate);
    							userLog.setClassName(targetName);
    							userLog.setMethodName(methodName);
    							userLog.setLogId(CommonUtil.generatePrimaryKey());
    							userLog.setTime(new Date());
    							HttpServletRequest request = (HttpServletRequest)((WebSubject)subject).getServletRequest();
    							userLog.setIp(CommonUtil.getIpAddr(request));
    							userLog.setUserId(CarfiUserUtil.getSysUser().getUserId());
    							SysUserLogService userLogService = (SysUserLogService) SpringUtil.getBean ("sysUserLogService");
    							userLogService.addUserLog(userLog);
    							break;
    						}
    					}
    				}
    			}
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    3 SpringUtil.java 解释

    SpringUtil的作用为在非Spring IOC容器下获取Spring IOC容器的上下文环境中的类,比如获取某个bean,最常见的如本文中的多次出现的“SysUserLogService userLogService = (SysUserLogService) SpringUtil.getBean (“sysUserLogService”);”通过SpringUtil得到service

    3.1 SpringUtil 配置详解

    SpringUtil.java

    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    
    /**
     * Spring工具类
     */
    public class SpringUtil implements ApplicationContextAware {
    
    	/** 上下文 */
    	private static ApplicationContext applicationContext;
    
    	public static ApplicationContext getApplicationContext() {
    		return applicationContext;
    	}
    
    	@Override
    	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    		SpringUtil.applicationContext = applicationContext;
    	}
    
    	/**
    	 * 根据Bean ID获取Bean
    	 * 
    	 * @param beanId
    	 * @return
    	 */
    	public static Object getBean(String beanId) {
    		if(applicationContext == null){
    			return null;
    		}
    		return applicationContext.getBean(beanId);
    	}
    }
    

    spring 配置文件(application.xml)中配置相应的bean,在配置文件中注册的SpringUtil bean 应在spring扫描配置之前

     <bean id="springUtil" class="XXX.XXXX.SpringUtil"></bean>
    
     <context:component-scan base-package="XXX.XXX.service"></context:component-scan>
    
    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 139,222
精华内容 55,688
关键字:

如何讲解自己的设计方案