CRM 项目实战-笔记
CRM项目: 编程思想和编程习惯
web项目的开发:如何分析、设计、编码、测试
技术架构
- 视图层(View)
展示数据,跟用户交互
html,css,js,jquery,bootstrap(ext|easyUI),jsp
- 控制层(Controller)
控制业务处理流程(接受请求、接收参数、封装参数;根据不同的请求调用业务层处理业务;根据处理结果,返回相应信息)
servlet,springMVC(webwork,struts1,struts2)
- 业务层(Service)
处理业务逻辑(处理业务的步骤以及操作的原子性)
JavaSE(工作流:activity|JBPM)
1. 添加学生
2. 记录操作日志
- 持久层(Dao/Mapper)
访问数据库
(jdbc),MyBatis,Hibernate,MyBatis-plus
- 整合层
处理类资源,处理类与类的关系,维护数据库资源
spring(IoC,AOP),(ejb,corba)
软件公司的组织结构
研发部(程序员、美工、DBA)、测试部、产品部、实施部、运维部、市场部
软件开发的生命周期
- 招标
投标: ----> 标书
甲方:
乙方:
- 可行性分析 -----> 可行性分析报告
技术方面能不能做这个项目? 经济 + 技术
- 需求分析 --------> 需求文档
产品经理,需求调研
项目原型: 容易确定需求,开发项目作为jsp网页
- 分析与设计
架构设计 --------> 架构文档
物理架构设计:
应用服务器:
tomcat (apache), weblogic(bea-->oracle), websphere (ibm), jboss(redhat), resin(MS).
web javaee: 13种协议 (servlet,jsp,xml,jdbc),mq + 标准
数据库服务器: mysql,oracle,DB2,sqlserver,达梦
逻辑架构设计: 代码分层
视图层-->控制层-->业务层-->持久层-->数据库
技术选型: java, .net
项目设计 ----------> 项目设计文档
物理模型设计: 表、字段、字段的类型和长度、以及表和表之间的关系
powerdesigner ----> xxxx.dpm
逻辑模型设计: 类、属性、方法、方法的参数和返回值,以及类与类之间的关系
relational rose -----> xxx.pdl
界面设计: 企业级应用 朴素. ---------> 项目原理
互联网应用 炫酷
算法设计: ----> 算法设计文档
- 搭建开发环境 ------> 技术架构文档
创建项目,添加jar包,添加配置文件,添加静态页面,添加公共类以及其它资源;
能够正常启动正常运行
- 编码实现 ---------> 注释
- 测试 ---------------> 测试用例
- 试运行 1-2周 ----> 使用手册
- 上线 ---------------> 实施手册
- 运维 ---------------> 运维手册
- 文档编纂
CRM 项目的核心业务
- CRM项目的简介 (Customer Relationship Management 客户关系管理系统)
企业级应用: 传统应用(另外一个分类: 互联网项目; 给销售或者贸易型公司使用,在市场,销售,客服等各个环节种维护客户关系,CRM项目的宗旨,增加新客户,留住老客户,把已有客户转换为中忠诚客户。
- CRM是一类项目
我们的CRM是给一个大型的进出口贸易公司来使用的,做大众商品的进出口贸易;商品受国家管制的
- CRM项目的核心业务
系统管理功能: 不是直接处理业务数据,为了保证业务管理的功能正常安全运行而设计的功能
用户登录,安全退出,登录验证等
超级管理员,开发和运维人员使用
业务管理功能: 处理业务数据
市场活动: 市场部,设计市场活动营销获取
线索: 销售部(初级销售)增加线索
客户和联系人:销售部(高级销售)有效地区分和跟踪客户和联系人
交易:销售部(高级销售)更好地区分和统计交易的各个阶段
售后回访: 客服部,妥善安排售后回访,主动提醒
统计图表: 管理层,统计交易表中各个阶段的数据量(销售漏斗管理)
[web开发: 前台发送请求--->后台java--->请求驱动--->http(长连接协议)]
物理模型结构
CRM的表结构
表格名称 | 中文名称 |
tb1_user | 用户表 |
tb1_dic_type (system) | 数据字典类型表 |
tb1_dic_value (system) | 数据字典值 |
tb1_activity | 市场活动表 |
tb1_activity_remark | 市场活动备注表 |
tb1_clue | 线索表 |
tb1_clue_remark | 线索备注表 |
tb1_clue_activity_relation | 线索和市场活动的关联关系表 |
tb1_customer | 客户表 |
tb1_customer_remark | 客户备注表 |
tb1_contacts | 联系人表 |
tb1_contacts_remark | 联系人备注表 |
tb1_tran | 交易表 |
tb1_tran_remark | 交易备注表 |
tb1_tran_history | 交易历史表 |
tb1_task |
任务表 |
- 主键字段:在数据库表中,如果有一组字段能够唯一确定一条记录,则可以把他们设计成表的主键字,推荐使用一个字段做主键,而且推荐使用没有业务含义的字段做主键,比如:id等
主键字段的类型和长度(由主键值的生成方式来决定):
主键值的生成方式:
1. 自增: 借助数据库自身主键生成机制,
数值型 (number) 长度由数据量来决定
运行效率低
开发效率高
2. assighed: 程序员手动生成主键值,唯一非空,算法
high/low 算法: 数据型 长度由数据量决定
UUID: 字符串 长度是 32 位
3. 共享主键:由另一张表的类型和长度决定
4. 联合主键: 由多个字段的类型和长度决定
- 外键字段:用来确定表与表之间的关系
1. 一对多: 一张表(A)中的一条记录可以对应另一张表(B)中的多条记录 ----> A(1) ----- B(n);
另一张表(B)中的一条记录只能对应一张表(A)中的一条记录 ----> B(1) ----- A(1)
添加数据时,先添加父类记录,再添加子类记录
删除数据时,先删除子类记录,再删除父类记录
查询数据时,可能进行关联查询 // 查询所有姓张的id,name和所在班级name
内连接:查询所有符合条件的数据,并且要求结果再两张表中都有相对应的记录
左外连接: 查询左侧表中所有复合条件的数据,即使在右侧表中没有相对应的记录
* 如果外键不能为空,优先使用内连接
如果外键可以为空,
-- 假如只需要查询那些在另一种张表中有相对应的记录,使用内连接
-- 假如需要查询左侧表中所有符合条件的记录,使用左外连接
2. 一对一: 一张表(A)中的一条记录可以对应另一张表(B)中的一条记录 ----> A(1) ----- B(1);
另一张表(B)中的一条记录只能对应一张表(A)中的一条记录 ----> B(1) ----- A(1)
· 1. 共享主键: (不推荐)
添加数据时,先添加先产生的表,再添加后产生的表记录
删除数据时,先删除后产生的表记录,再删除先产生的表记录
查询数据时,无需进行连接查询
2. 唯一外键:
*一对一就是特殊的一对多的关系
*操作和一对多完全一样
3. 多对多: 一张表(A)中的多条记录可以对应另一张表(B)中的多条记录 ----> A(n) ----- B(n);
另一张表(B)中的多条记录只能对应一张表(A)中的多条记录 ----> B(n) ----- A(n)
添加数据时,先添加父类记录(tb1_student, tb2_course) 再添加子表 (tb1_student_course_relation)
删除数据时,先删除子类记录,再删除父类记录
查询数据时,可能会进行关联查询
- 关于日期和时间的字段的字段
都按照字符串处理:
char(10) yyyy-mm-dd
char(19) yyyy-mm-dd HH:mm:ss
创建crm的数据库实例
1> 创建数据库
2> 执行sql脚本
- 执行 tb1_user.sql
-- ----------------------------
-- Table structure for `tbl_user`
-- ----------------------------
DROP TABLE IF EXISTS `tbl_user`;
CREATE TABLE `tbl_user` (`id` char(32) NOT NULL COMMENT 'uuid\r\n ',`login_act` varchar(255) DEFAULT NULL,`name` varchar(255) DEFAULT NULL,`login_pwd` varchar(255) DEFAULT NULL COMMENT '密码不能采用明文存储,采用密文,MD5加密之后的数据',`email` varchar(255) DEFAULT NULL,`expire_time` char(19) DEFAULT NULL COMMENT '失效时间为空的时候表示永不失效,失效时间为2018-10-10 10:10:10,则表示在该时间之前该账户可用。',`lock_state` char(1) DEFAULT NULL COMMENT '锁定状态为空时表示启用,为0时表示锁定,为1时表示启用。',`deptno` char(4) DEFAULT NULL,`allow_ips` varchar(255) DEFAULT NULL COMMENT '允许访问的IP为空时表示IP地址永不受限,允许访问的IP可以是一个,也可以是多个,当多个IP地址的时候,采用半角逗号分隔。允许IP是192.168.100.2,表示该用户只能在IP地址为192.168.100.2的机器上使用。',`createTime` char(19) DEFAULT NULL,`create_by` varchar(255) DEFAULT NULL,`edit_time` char(19) DEFAULT NULL,`edit_by` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of tbl_user
-- ----------------------------
INSERT INTO `tbl_user` VALUES ('06f5fc056eac41558a964f96daa7f27c', 'ls', '李四', 'yf123', 'ls@163.com', '2018-11-27 21:50:05', '1', 'A001', '192.168.1.1,0:0:0:0:0:0:0:1', '2018-11-22 12:11:40', '李四', null, null);
INSERT INTO `tbl_user` VALUES ('40f6cdea0bd34aceb77492a1656d9fb3', 'zs', '张三', 'yf123', 'zs@qq.com', '2018-11-30 23:50:55', '1', 'A001', '192.168.1.1,192.168.1.2,127.0.0.1,0:0:0:0:0:0:0:1', '2018-11-22 11:37:34', '张三', null, null);
- 执行 dictionary.sql
/*==============================================================*/
/* DBMS name: MySQL 5.0 */
/* Created on: 2020/3/5 9:12:54 */
/*==============================================================*/drop table if exists tbl_dic_type;drop table if exists tbl_dic_value;/*==============================================================*/
/* Table: tbl_dic_type */
/*==============================================================*/
create table tbl_dic_type
(code varchar(255) not null comment '编码是主键,不能为空,不能含有中文。',name varchar(255),description varchar(255),primary key (code)
);/*==============================================================*/
/* Table: tbl_dic_value */
/*==============================================================*/
create table tbl_dic_value
(id char(32) not null comment '主键,采用UUID',value varchar(255) comment '不能为空,并且要求同一个字典类型下字典值不能重复,具有唯一性。',text varchar(255) comment '可以为空',order_no varchar(255) comment '可以为空,但不为空的时候,要求必须是正整数',type_code varchar(255) comment '外键',primary key (id)
);
- 执行 crm-3_市场活动.sql
drop table if exists tbl_activity;drop table if exists tbl_activity_remark;/*==============================================================*/
/* Table: tbl_activity */
/*==============================================================*/
create table tbl_activity
(id char(32) not null,owner char(32),name varchar(255),start_date char(10),end_date char(10),cost varchar(255),description varchar(255),create_time char(19),create_by varchar(255),edit_time char(19),edit_by varchar(255),primary key (id)
);/*==============================================================*/
/* Table: tbl_activity_remark */
/*==============================================================*/
create table tbl_activity_remark
(id char(32) not null,note_content varchar(255),create_time char(19),create_by varchar(255),edit_time char(19),edit_by varchar(255),edit_flag char(1) comment '0表示未修改,1表示已修改',activity_id char(32),primary key (id)
);
- 执行 crm-4_线索_客户_联系人_交易.sql
drop table if exists tbl_clue;drop table if exists tbl_clue_activity_relation;drop table if exists tbl_clue_remark;drop table if exists tbl_contacts;drop table if exists tbl_contacts_activity_relation;drop table if exists tbl_contacts_remark;drop table if exists tbl_customer;drop table if exists tbl_customer_remark;drop table if exists tbl_tran;drop table if exists tbl_tran_history;drop table if exists tbl_tran_remark;/*==============================================================*/
/* Table: tbl_clue */
/*==============================================================*/
create table tbl_clue
(id char(32) not null,fullname varchar(255),appellation varchar(255),owner char(32),company varchar(255),job varchar(255),email varchar(255),phone varchar(255),website varchar(255),mphone varchar(255),state varchar(255),source varchar(255),create_by varchar(255),create_time char(19),edit_by varchar(255),edit_time char(19),description varchar(255),contact_summary varchar(255),next_contact_time char(10),address varchar(255),primary key (id)
);/*==============================================================*/
/* Table: tbl_clue_activity_relation */
/*==============================================================*/
create table tbl_clue_activity_relation
(id char(32) not null,clue_id char(32),activity_id char(32),primary key (id)
);/*==============================================================*/
/* Table: tbl_clue_remark */
/*==============================================================*/
create table tbl_clue_remark
(id char(32) not null,note_content varchar(255),create_by varchar(255),create_time char(19),edit_by varchar(255),edit_time char(19),edit_flag char(1),clue_id char(32),primary key (id)
);/*==============================================================*/
/* Table: tbl_contacts */
/*==============================================================*/
create table tbl_contacts
(id char(32) not null,owner char(32),source varchar(255),customer_id char(32),fullname varchar(255),appellation varchar(255),email varchar(255),mphone varchar(255),job varchar(255),create_by varchar(255),create_time char(19),edit_by varchar(255),edit_time char(19),description varchar(255),contact_summary varchar(255),next_contact_time char(10),address varchar(255),primary key (id)
);/*==============================================================*/
/* Table: tbl_contacts_activity_relation */
/*==============================================================*/
create table tbl_contacts_activity_relation
(id char(32) not null,contacts_id char(32),activity_id char(32),primary key (id)
);/*==============================================================*/
/* Table: tbl_contacts_remark */
/*==============================================================*/
create table tbl_contacts_remark
(id char(32) not null,note_content varchar(255),create_by varchar(255),create_time char(19),edit_by varchar(255),edit_time char(19),edit_flag char(1),contacts_id char(32),primary key (id)
);/*==============================================================*/
/* Table: tbl_customer */
/*==============================================================*/
create table tbl_customer
(id char(32) not null,owner char(32),name varchar(255),website varchar(255),phone varchar(255),create_by varchar(255),create_time char(19),edit_by varchar(255),edit_time char(19),contact_summary varchar(255),next_contact_time char(10),description varchar(255),address varchar(255),primary key (id)
);/*==============================================================*/
/* Table: tbl_customer_remark */
/*==============================================================*/
create table tbl_customer_remark
(id char(32) not null,note_content varchar(255),create_by varchar(255),create_time char(19),edit_by varchar(255),edit_time char(19),edit_flag char(1),customer_id char(32),primary key (id)
);/*==============================================================*/
/* Table: tbl_tran */
/*==============================================================*/
create table tbl_tran
(id char(32) not null,owner char(32),money varchar(255),name varchar(255),expected_date char(10),customer_id char(32),stage varchar(255),type varchar(255),source varchar(255),activity_id char(32),contacts_id char(32),create_by varchar(255),create_time char(19),edit_by varchar(255),edit_time char(19),description varchar(255),contact_summary varchar(255),next_contact_time char(10),primary key (id)
);/*==============================================================*/
/* Table: tbl_tran_history */
/*==============================================================*/
create table tbl_tran_history
(id char(32) not null,stage varchar(255),money varchar(255),expected_date char(10),create_time char(19),create_by varchar(255),tran_id char(32),primary key (id)
);/*==============================================================*/
/* Table: tbl_tran_remark */
/*==============================================================*/
create table tbl_tran_remark
(id char(32) not null,note_content varchar(255),create_by varchar(255),create_time char(19),edit_by varchar(255),edit_time char(19),edit_flag char(1),tran_id char(32),primary key (id)
);
3> 查看数据库列表
搭建开发环境
创建项目
1> 创建crm_project (设置JDK)
2> 创建maven模型 webapp
3> 创建目录结构+框架类型
3-1> 目录结构
3-2> 设置文件夹类型
4> 添加相关依赖
MySQL 驱动:
<!-- MySQL数据库连接驱动 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.43</version>
</dependency>
JDBC 数据源连接: Druid
<!-- JDBC数据源连接池 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.1</version>
</dependency>
MyBatis 框架依赖
<!-- MyBatis框架依赖 -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.1</version>
</dependency>
Spring 相关依赖配置
<!-- Spring框架依赖的JAR配置 -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-oxm</artifactId><version>4.3.9.RELEASE</version>
</dependency>
Spring AOP 依赖
<!-- Spring AOP支持-->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version>
</dependency>
MyBatis 与 Spring 整合依赖
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.0</version>
</dependency>
添加项目对JSP的支持
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version>
</dependency>
<dependency><groupId>javax.servlet.jsp.jstl</groupId><artifactId>jstl-api</artifactId><version>1.2</version>
</dependency><dependency><groupId>org.apache.taglibs</groupId><artifactId>taglibs-standard-spec</artifactId><version>1.2.1</version>
</dependency>
<dependency><groupId>org.apache.taglibs</groupId><artifactId>taglibs-standard-impl</artifactId><version>1.2.1</version>
</dependency>
添加Jackson的插件依赖
<!-- 加载jackson插件依赖 -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.7.3</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.7.3</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.7.3</version>
</dependency>
添加poi的插件
<!--poi依赖-->
<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.15</version>
</dependency>
文件上传依赖
<!-- 文件上传 -->
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version>
</dependency>
Log4j2依赖
<!-- Log4j2依赖的JAR配置 -->
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId>
<version>2.3</version>
</dependency>
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.3</version>
</dependency>
5> 设置字符集格式
6> 添加配置文件
Mybatis 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><settings><setting name="logImpl" value="STDOUT_LOGGING"/></settings><typeAliases><package name="com.bjpowernode.crm.model"/></typeAliases><mappers><package name="com.bjpowernode.crm.mapper"/></mappers>
</configuration>
配置事件和业务
<?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:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"><!-- 配置数据源 --><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="username" value="root"/><property name="password" value="123456"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/crm2008?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/></bean><!-- 配置SqlSessionFactory --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- 必须注入属性dataSource --><property name="dataSource" ref="dataSource"/><!-- 如果mybatis没有特殊的配置(比如别名等),configLocation可以省去 ;否则,不能省略--><property name="configLocation" value="classpath:mybatis-config.xml"/></bean><!-- mapper注解扫描器配置,扫描@MapperScan注解,自动生成代码对象 --><bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.bjpowernode.crm.mapper"/><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/></bean><!-- 配置事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!-- 配置事务 --><aop:config><aop:pointcut expression="execution(* com.bjpowernode.crm..service.*.*(..))" id="allMethodPointcut"/><aop:advisor advice-ref="txAdvice" pointcut-ref="allMethodPointcut"/></aop:config><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="add*" propagation="REQUIRED" rollback-for="Exception"/><tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/><tx:method name="edit*" propagation="REQUIRED" rollback-for="Exception"/><tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/><tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/><tx:method name="do*" propagation="REQUIRED" rollback-for="Exception"/><tx:method name="*" propagation="REQUIRED" read-only="true"/></tx:attributes></tx:advice>
</beans>
SpringMVC 配置文件
<?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:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd"><!-- dispatcherServlet截获所有URL请求 --><mvc:default-servlet-handler /><!-- spring mvc 扫描包下的controller --><context:component-scan base-package="com.bjpowernode.crm.web.controller"/><!-- 配置注解驱动 --><mvc:annotation-driven/><!-- 配置视图解析器 --><bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/pages/"/><property name="suffix" value=".jsp"/></bean><!-- 配置文件上传解析器 id:必须是multipartResolver--><!--<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><property name="maxUploadSize" value="#{1024*1024*80}"/><property name="defaultEncoding" value="utf-8"/></bean>-->
</beans>
Spring总配置文件
<?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:p="http://www.springframework.org/schema/p"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:task="http://www.springframework.org/schema/task"xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.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"><!-- 加载系统配置文件<context:property-placeholder location="classpath:*.properties" />--><!-- 扫描注解 --><context:component-scan base-package="com.bjpowernode.crm.service" /><!-- 导入数据相关配置 --><import resource="applicationContext-datasource.xml" />
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"id="dataservice" version="3.0"><display-name>dataservice application</display-name><!-- spring监听器加载applicationContext.xml配置文件 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- spring字符过滤器 --><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- Spring mvc分发servlet --><servlet><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext-mvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping><servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>*.do</url-pattern></servlet-mapping><!-- 欢迎页,默认进入index controller --><welcome-file-list><welcome-file>/</welcome-file></welcome-file-list>
</web-app>
配置web.xml
<resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>src/main/resources</directory><includes><include>**/*.*</include></includes></resource></resources>
- web应用根目录下的内容都是不安全的,外界可以通过url直接访问;所以,一般为了数据的安全,都会把页面放在web-INF下: 因为web-INF目录下的资源是受保护的,外界是不能直接访问的
配置服务器 (Tomcat)
7> 将项目部署到tomcat上
8> 启动服务器
因为还没对接上controller传输页面,所以目前是404状态
首页
分析需求
用户访问项目首页,首先进入登录页面。
分析与设计
编码实现
1> IndexController.java
2> index.jsp
3> UserController.java
4> qx/user/login.jsp
测试
用户登录
分析需求
用户在登录页面,输入用户名和密码,点击"登录"按钮或者回车,完成用户登录的功能:
用户名和密码不能为空
用户名或者密码错误,用户已过期,用户状态被锁定,ip受限都不能登录成功
登录成功之后,所有业务页面显示当前用户的名称
实现10天记住密码
登录成功之后,跳转到业务主页面
登录失败,页面不跳转,提示信息
Tips1: a) 同步请求和异步请求的区别
同步请求:浏览器窗口发出的请求,响应信息返回到浏览器窗口,所以会进行全局刷新。
异步请求:ajax发出的请求,响应信息返回到ajax的回调函数,既可以进行全局刷新,也可以进行局部刷新。小结:如果需要进行全局刷新,推荐使用同步请求,当然也可以使用异步请求;
如果需要进行局部刷新,只能使用异步请求;
如果既可能进行全局刷新,也可能进行局部刷新,也是只能使用异步请求。Tips2:b) mybatis逆向工程
1)简介:根据表生成mapper层三部分代码:实体类,mapper接口,映射文件。
2)使用mybatis逆向工程:
a)创建工程:crm-mybatis-generator
b)添加插件:Tips3:使用jquery获取指定元素的指定属性的值
选择器.attr("属性名");//用来获取那些值不是true/false的属性的值.
选择器.prop("属性名");//用来获取值是true/false的属性的值.例如:checked,selected,readonly,disabled等。
Tips4: 把控制层(controller)代码中处理好的数据传递到视图层(jsp),使用作用域传递
pageContext:用来在同一个页面的不同标签之间传递数据。
request:在同一个请求过程中间传递数据。
session: 同一个浏览器窗口的不同请求之间传递数据。
application:所有用户共享的数据,并且长久频繁使用的数据。<c:aaaa>
<c:bbbb>Tips5: jquery事件函数的用法
选择器.click(function(){//给指定的元素添加事件
//js代码
});选择器.click();//在指定的元素上模拟发生一次事件
Tips6: 记住密码
访问:login.jsp---->后台:.html:如果上次记住密码,自动填上账号和密码;否则,不填。
如何判断上次是否记住密码?
--上次登录成功,判断是否需要记住密码:如果需要记住密码,则往浏览器写cookie;否则,删除cookie。
而且cookie的值必须是该用户的loginAct和loginPwd
--下次登录时,判断该用户有没有cookie:如果有,则自动填写账号和密码;否则,不写。
而且填写的是cookie的值.
----->浏览器显示Tips6-1: 获取cookie
1. 使用java代码获取cookie:Cookie[] cs=request.getCookies();for(Cookie c:cs){if(c.getName().equals("loginAct")){String loginAct=c.getValue();}else if(c.getName().equals("loginPwd")){String loginPwd=c.getValue();}}
2. 使用EL表达式获取cookie:
${cookie.loginAct.value}
${cookie.loginPwd.value}
<!--myBatis逆向工程插件--> <plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version> <configuration><verbose>true</verbose><overwrite>true</overwrite> </configuration> </plugin>
c) 添加配置文件:
数据库连接信息此处会发生报错,但是如果没有就直接忽略报错信息1是,数据库连接失败: 失败原因是: 本地存在的MySQL的版本与当前依赖当中的版本不同或者缺失我的电脑上使用的是8.0的版本的,所以配的依赖是8.0.xx版本,兼容有效 但是视频中配的版本是5.1.xx,所以版本号不匹配报错信息2是,时区没有正确设置,需要再url之后添加 + ?serverTimezone=UTC,已经在图中标出
代码保存的目录
表的信息
d) 运行mybatis的逆向工程,根据指定表生成java代码,保存到指定的目录中。
分析与设计
编码实现
1> UserController.java (异步请求: loginAct, loginPwd,isRemPwd;请求的协议: reponse, session)
1-1> returnObject.java
package com.bjpowernode.crm.commons.domain;public class ReturnObject {private String code;//处理成功获取失败的标记:1---成功,0---失败private String message;//提示信息private Object retData;//其它数据public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public Object getRetData() {return retData;}public void setRetData(Object retData) {this.retData = retData;}
}
1-2> 为优化代码: 日期动态信息
package com.bjpowernode.crm.commons.utils;import java.text.SimpleDateFormat;
import java.util.Date;/*** 对Date类型数据进行处理的工具类*/
public class DateUtils {/*** 对指定的date对象进行格式化: yyyy-MM-dd HH:mm:ss* @param date* @return*/public static String formateDateTime(Date date){SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String dateStr=sdf.format(date);return dateStr;}/*** 对指定的date对象进行格式化: yyyy-MM-dd* @param date* @return*/public static String formateDate(Date date){SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");String dateStr=sdf.format(date);return dateStr;}/*** 对指定的date对象进行格式化: HH:mm:ss* @param date* @return*/public static String formateTime(Date date){SimpleDateFormat sdf=new SimpleDateFormat("HH:mm:ss");String dateStr=sdf.format(date);return dateStr;}
}
UserController.java (完整的控制层代码)
@RequestMapping("/settings/qx/user/login.do")public @ResponseBody Object login(String loginAct, String loginPwd, String isRemPwd, HttpServletRequest request, HttpServletResponse response, HttpSession session){//封装参数Map<String,Object> map=new HashMap<>();map.put("loginAct",loginAct);map.put("loginPwd",loginPwd);//调用service层方法,查询用户User user=userService.queryUserByLoginActAndPwd(map);//根据查询结果,生成响应信息ReturnObject returnObject=new ReturnObject();if(user==null){//登录失败,用户名或者密码错误returnObject.setCode(Constants.RETURN_OBJECT_CODE_FAIL);returnObject.setMessage("用户名或者密码错误");}else{//进一步判断账号是否合法//user.getExpireTime() //2019-10-20// new Date() //2020-09-10if(DateUtils.formateDateTime(new Date()).compareTo(user.getExpireTime())>0){//登录失败,账号已过期returnObject.setCode(Constants.RETURN_OBJECT_CODE_FAIL);returnObject.setMessage("账号已过期");}else if("0".equals(user.getLockState())){//登录失败,状态被锁定returnObject.setCode(Constants.RETURN_OBJECT_CODE_FAIL);returnObject.setMessage("状态被锁定");} // ip判断模块,因为在登录的时候会进行判断ip地址,ip地址不同会导致登录失败,所以为了演示登录成功的界面,这里先默认注释掉这部分
// else if(!user.getAllowIps().contains(request.getRemoteAddr())){
// //登录失败,ip受限
// returnObject.setCode(Constants.RETURN_OBJECT_CODE_FAIL);
// returnObject.setMessage("ip受限");
// }else{//登录成功returnObject.setCode(Constants.RETURN_OBJECT_CODE_SUCCESS);//把user保存到session中//session.setAttribute("key",value(user.loginAct/loginPwd)); 设置属性//session.getAttribute("key"); 获取设置的属性,返回的类型值是一个 Objectsession.setAttribute(Constants.SESSION_USER,user);//如果需要记住密码,则往外写cookieif("true".equals(isRemPwd)){Cookie c1=new Cookie("loginAct",user.getLoginAct());c1.setMaxAge(10*24*60*60);response.addCookie(c1);Cookie c2=new Cookie("loginPwd",user.getLoginPwd());c2.setMaxAge(10*24*60*60);response.addCookie(c2);}else{//把没有过期cookie删除Cookie c1=new Cookie("loginAct","1");c1.setMaxAge(0);response.addCookie(c1);Cookie c2=new Cookie("loginPwd","1");c2.setMaxAge(0);response.addCookie(c2);}}}return returnObject;}
2> login.jsp (传参+封装参数+记住密码+接受客户端的请求+发送跳转请求:完整的前端页面代码)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%String basePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/";
%>
<html>
<head><base href="<%=basePath%>">
<meta charset="UTF-8">
<link href="jquery/bootstrap_3.3.0/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="jquery/jquery-1.11.1-min.js"></script>
<script type="text/javascript" src="jquery/bootstrap_3.3.0/js/bootstrap.min.js"></script>
<script type="text/javascript">$(function () {//给整个浏览器窗口添加键盘按下事件$(window).keydown(function (e) {//如果按的是回车键,则提交登录请求if(e.keyCode==13){$("#loginBtn").click();}});//给"登录"按钮添加单击事件$("#loginBtn").click(function () {//收集参数var loginAct=$.trim($("#loginAct").val());var loginPwd=$.trim($("#loginPwd").val());var isRemPwd=$("#isRemPwd").prop("checked");//表单验证if(loginAct==""){alert("用户名不能为空");return;}if(loginPwd==""){alert("密码不能为空");return;}//显示正在验证//$("#msg").text("正在努力验证....");//发送请求$.ajax({url:'settings/qx/user/login.do',data:{loginAct:loginAct,loginPwd:loginPwd,isRemPwd:isRemPwd},type:'post',dataType:'json',success:function (data) {if(data.code=="1"){//跳转到业务主页面window.location.href="workbench/index.do";}else{//提示信息$("#msg").text(data.message);}},beforeSend:function () {//当ajax向后台发送请求之前,会自动执行本函数;//该函数的返回值能够决定ajax是否真正向后台发送请求://如果该函数返回true,则ajax会真正向后台发送请求;否则,如果该函数返回false,则ajax放弃向后台发送请求。$("#msg").text("正在努力验证....");return true;}});});});
</script>
</head>
<body><div style="position: absolute; top: 0px; left: 0px; width: 60%;"><img src="data:image/IMG_7114.JPG" style="width: 100%; height: 90%; position: relative; top: 50px;"></div><div id="top" style="height: 50px; background-color: #3C3C3C; width: 100%;"><div style="position: absolute; top: 5px; left: 0px; font-size: 30px; font-weight: 400; color: white; font-family: 'times new roman'">CRM <span style="font-size: 12px;">©2019 动力节点</span></div></div><div style="position: absolute; top: 120px; right: 100px;width:450px;height:400px;border:1px solid #D5D5D5"><div style="position: absolute; top: 0px; right: 60px;"><div class="page-header"><h1>登录</h1></div><form action="workbench/index.html" class="form-horizontal" role="form"><div class="form-group form-group-lg"><div style="width: 350px;"><input class="form-control" id="loginAct" type="text" value="${cookie.loginAct.value}" placeholder="用户名"></div><div style="width: 350px; position: relative;top: 20px;"><input class="form-control" id="loginPwd" type="password" value="${cookie.loginPwd.value}" placeholder="密码"></div><div class="checkbox" style="position: relative;top: 30px; left: 10px;"><label><c:if test="${not empty cookie.loginAct and not empty cookie.loginPwd}"><input type="checkbox" id="isRemPwd" checked></c:if><c:if test="${empty cookie.loginAct or empty cookie.loginPwd}"><input type="checkbox" id="isRemPwd"></c:if>十天内免登录</label> <span id="msg" style="color: red"></span></div><button type="button" id="loginBtn" class="btn btn-primary btn-lg btn-block" style="width: 350px; position: relative;top: 45px;">登录</button></div></form></div></div>
</body>
</html>
3> UserService 接口
实现接口
Mapper 执行自定义 sql 语句
自定义 UserMapper.xml
4> WorkBenchIndexController.java (实现跳转)
package com.bjpowernode.crm.workbench.web.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
public class WorkbenchIndexController {@RequestMapping("/workbench/index.do")public String index(){//跳转到业务主页面return "workbench/index";}
}
5> 十天内免密登录
5-1> 优化 UserController.java: 在登录成功的模块下添加下列代码多复选框是否记住密码进行判断处理
5-2> login.jsp 前端收集后端是否点击复选框的请求: cookie.loginAct.value = 1; cookie.loginPwd.value = 1 checked 生效
测试
输入用户名和密码(数据库里面有):点击登录按钮,成功登录到主界面当中
可能发生的登录失败: 原因是MySQL的版本太高了,需要降版本,本次使用的版本是 MySQL 5.1.35
安全退出
分析需求
用户在任意的业务页面,点击"退出"按钮,弹出确认退出的模态窗口;用户在确认退出的模态窗口,点击"确定"按钮,完成安全退出的功能.
安全退出: 清空cookie,销毁session
退出完成之后,跳转到首页
分析与设计
编码实现
1> UseController.java
Tips1: 登录验证
1)过滤器
a)implements Filter{
--init
--doFilter
--destroy
}
b)配置过滤器:web.xml
2)拦截器
a)提供拦截器类:implements HandlerInterceptor{
--pre
--post
--after
}
b)配置拦截器:springmvc.xml (下面有代码展示)
@RequestMapping("/settings/qx/user/logout.do")public String logout(HttpServletResponse response,HttpSession session){//清空cookieCookie c1=new Cookie("loginAct","1");c1.setMaxAge(0);response.addCookie(c1);Cookie c2=new Cookie("loginPwd","1");c2.setMaxAge(0);response.addCookie(c2);//销毁sessionsession.invalidate();//跳转到首页return "redirect:/";//借助springmvc来重定向,response.sendRedirect("/crm/");}
Tips2: 拦截器的作用
对登录的部分不起作用,但是对于已经登录页面的模块有权限拦截的作用,符合要求的访问者才可以访问页面,不仅仅是输一个网页地址就可以访问到页面
登录验证
用户访问任何业务资源,都需要进行登录验证
只有登录成功的用户才能访问业务资源
没有登录成功的用户访问业务资源,跳转到登录页面
LoginInterceptor.java
package com.bjpowernode.crm.settings.web.interceptor;import com.bjpowernode.crm.commons.constants.Constants;
import com.bjpowernode.crm.settings.domain.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {//如果用户没有登录成功,则跳转到登录页面HttpSession session=httpServletRequest.getSession();User user=(User) session.getAttribute(Constants.SESSION_USER);if(user==null){String redirect=httpServletRequest.getContextPath(); //""httpServletResponse.sendRedirect(redirect);//重定向时,url必须加项目的名称return false;}return true;}@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {}
}
applicationContext-mvc.xml
利用 mvc 来配置拦截器
<mvc:interceptors><mvc:interceptor><!--配置拦截的请求--><mvc:mapping path="/settings/**"/><mvc:mapping path="/workbench/**"/><!--配置排除拦截的请求(优先级高)--><mvc:exclude-mapping path="/settings/qx/user/toLogin.do"/><mvc:exclude-mapping path="/settings/qx/user/login.do"/><!--拦截器类--><bean class="com.bjpowernode.crm.settings.web.interceptor.LoginInterceptor"/></mvc:interceptor>
</mvc:interceptors>
测试
点击退出
重定向(redirect)在首页
问题解决: 登陆首页之后发现主页面是404,原因没有为这一部分设置跳转页面
控制层接入
package com.bjpowernode.crm.workbench.web.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
public class WorkbenchIndexController {@RequestMapping("/workbench/index.do")public String index(){//跳转到业务主页面return "workbench/index";}
}
测试
CRM 项目实战-笔记相关推荐
- Unity3D项目实战笔记(10):Unity3D编译IPA的PostEvents–节约时间利器
最近,SDK支付等接入差不多了,就从Unity3D生成IPA (企业版License), 然,需要手动执行的PostEvents竟然多大10项+, 这些我默默的承受了1周时间,每次约浪费20分钟-额外 ...
- 1.vue项目实战笔记(已完结)
vue项目实战笔记 目标 目录 1.项目概述 1.1电商项目基本业务概述 1.2电商后台管理系统的功能 1.3电商后台管理系统的开发模式(前后端分离) 1.4电商后台管理系统的技术选型 1.前端项目技 ...
- 备战金九银十,阿里P8师兄指导完整攻略(附:学习资料+面试宝典+项目实战笔记)
前言 还剩下两个月就到了金九银十,一般来说,秋招的含金量明显是高于春招的. 那么如何准备即将到来的面试热潮呢?运筹帷幄之后,决胜千里之外! 坚决不打毫无准备的仗是小编的原则:不论是笔试还是面试都是有章 ...
- Vue全家桶-项目实战笔记
写在前面 这是我跟着黑马程序员的Vue全家桶-项目实战教程写的一篇笔记,主要记录思路,内容不完整,这里只写到了用户列表管理,进来的友友们可以根据目录,看是否有自己需要的功能 这篇笔记记录的是基础vue ...
- CRM项目实战第一天
总纲: 1.项目分析 2.项目设计 3.编码 4.测试 CRM项目(客户管理系统项目) 项目采用的技术架构: 视图层(view):展示数据,与用户交互.html.css.js.jquery.boots ...
- 悟空CRM项目实战(6)
新的一周开始,我们的悟空CRM项目系统测试也进入一个新的阶段--测试用例执行阶段,针对上周的测试用例编写情况,本着交叉执行的原则,我分到了前台的项目管理和客户管理两个模块,由于是新的模块,所以我对其也 ...
- 阿里P8架构师力荐K8s项目实战笔记 图文并茂带你深度解析Kubernetes
一.前言 Kubernetes(简称K8S)是开源的容器集群管理系统,可以实现容器集群的自动化部署.自动扩缩容.维护等功能.它既是一款容器编排工具,也是全新的基于容器技术的分布式架构领先方案.在Doc ...
- 添加防火墙策略_网络安全文章基础篇(第5张)防火墙项目实战笔记
1.项目一 目标 实现 client1 可以访问 server1 的http (NAT) 实现 server1 可以ping通 server2 (服务器映射) 环境: ...
- K8s项目实战笔记 图文并茂带你深度解析Kubernetes
一.前言 Kubernetes(简称K8S)是开源的容器集群管理系统,可以实现容器集群的自动化部署.自动扩缩容.维护等功能.它既是一款容器编排工具,也是全新的基于容器技术的分布式架构领先方案.在Doc ...
最新文章
- 命令提示符(cmd)中的tracert命令使用
- python课程将主要介绍哪些内容-熊学堂 · 人工智能 | 课程介绍
- EventBank闪耀企业服务 荣获“2017中国企业服务云年度产品”奖
- Ribbon的权重负载均衡策略
- php函数中首次出现,PHP开发之子串在字符串中首次出现、最后出现、出现次数函数相关总结...
- linux扩展根路径,Linux虚拟机根(/)目录扩容
- 像excel一样规律填充(二)
- python复制csv数据_如何使用Python将CSV数据复制到现有xlsx文件
- 【cvpr2022】ReSTR: Convolution-free Referring Image Segmentation Using Transformers
- 如何判断一个网站地址是否可以安全访问?
- xHiveAI-A311D:AI开发套件
- vue实现手机通讯录效果
- android nougat honr7,Honor 荣耀7 最新全功能NFC 体验
- springboot+dubbo+redis+RabbitMQ 项目整合实例【附完整源码】
- Python编程:输入两个整数A,B 计算A+B的结果
- PlecsMMA学习2
- 在线图片处理工具锦集,满满干货
- Win32 .Net QA
- 各大互联网公司架构汇总
- matlab曲面如何导入proe,Proe如何通过导入外部数据创建曲面和曲线?