1. 项目概述:SSM框架下的个人通讯录系统
去年接手公司内部通讯录重构项目时,我选择了SSM框架作为技术基底。这个看似简单的个人通讯录系统,实际上涉及了企业级应用开发的完整技术链条。不同于市面上现成的通讯录软件,自主开发的系统在数据安全、功能定制和扩展性方面具有独特优势。下面我将从架构设计到具体实现,完整还原这个典型的企业级Java Web项目开发过程。
2. 技术选型与架构设计
2.1 SSM框架组合解析
选择Spring+SpringMVC+MyBatis的组合主要基于三个技术考量:
-
Spring的IoC容器管理着整个应用的Bean生命周期,通过注解配置方式简化了传统的XML配置。实际开发中特别依赖它的声明式事务管理(@Transactional),这在处理通讯录的批量导入导出时尤为重要。
-
SpringMVC采用前端控制器模式,通过@Controller注解完美处理HTTP请求。我们团队特别看重它的拦截器机制,实现了通讯录的权限验证(如部门隔离访问控制)。
-
MyBatis的灵活SQL编写能力适合复杂查询场景。通讯录中常见的多条件组合查询(姓名+部门+职位筛选),用动态SQL标签(
、 )实现起来非常高效。
2.2 数据库设计要点
通讯录系统的MySQL表结构设计遵循以下原则:
sql复制CREATE TABLE `contact` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL COMMENT '姓名',
`mobile` varchar(15) NOT NULL COMMENT '手机号',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`company` varchar(50) DEFAULT NULL COMMENT '公司',
`department` varchar(30) DEFAULT NULL COMMENT '部门',
`position` varchar(30) DEFAULT NULL COMMENT '职位',
`tags` varchar(100) DEFAULT NULL COMMENT '标签(逗号分隔)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计决策:手机号设为唯一索引(业务上保证不重复),tags字段采用逗号分隔的字符串存储而非关联表(简化小型系统实现)
3. 核心功能实现细节
3.1 联系人管理模块
控制器层采用RESTful风格设计:
java复制@RestController
@RequestMapping("/api/contact")
public class ContactController {
@Autowired
private ContactService contactService;
@PostMapping
public Result addContact(@Valid @RequestBody Contact contact) {
return contactService.addContact(contact);
}
@GetMapping("/{id}")
public Result getContact(@PathVariable Integer id) {
return Result.success(contactService.getById(id));
}
@GetMapping("/search")
public Result searchContacts(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String department) {
return contactService.searchContacts(keyword, department);
}
}
服务层实现业务逻辑:
java复制@Service
public class ContactServiceImpl implements ContactService {
@Override
@Transactional
public Result addContact(Contact contact) {
// 手机号重复校验
if (contactMapper.existsByMobile(contact.getMobile())) {
throw new BusinessException("手机号已存在");
}
contactMapper.insert(contact);
return Result.success(contact.getId());
}
}
3.2 复杂查询的MyBatis实现
动态SQL构建示例:
xml复制<select id="selectByCondition" resultType="Contact">
SELECT * FROM contact
<where>
<if test="keyword != null and keyword != ''">
AND (name LIKE CONCAT('%',#{keyword},'%')
OR mobile LIKE CONCAT('%',#{keyword},'%'))
</if>
<if test="department != null and department != ''">
AND department = #{department}
</if>
</where>
ORDER BY create_time DESC
</select>
4. 前端交互方案
4.1 Thymeleaf模板集成
虽然现在前后端分离是主流,但考虑到通讯录系统的管理后台需要快速迭代,我们选择了Thymeleaf:
html复制<table class="table">
<tr th:each="contact : ${contacts}">
<td th:text="${contact.name}"></td>
<td th:text="${contact.mobile}"></td>
<td>
<a th:href="@{/contact/edit/{id}(id=${contact.id})}"
class="btn btn-sm btn-primary">编辑</a>
</td>
</tr>
</table>
4.2 分页查询实现
PageHelper插件的典型配置:
java复制@GetMapping
public String listContacts(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
Model model) {
PageHelper.startPage(pageNum, pageSize);
List<Contact> contacts = contactService.listAll();
PageInfo<Contact> pageInfo = new PageInfo<>(contacts);
model.addAttribute("pageInfo", pageInfo);
return "contact/list";
}
5. 项目部署与优化
5.1 多环境配置方案
通过Spring Profile实现:
properties复制# application-dev.properties
spring.datasource.url=jdbc:mysql://localhost:3306/contact_dev
spring.datasource.username=devuser
# application-prod.properties
spring.datasource.url=jdbc:mysql://prod-db:3306/contact_prod
spring.datasource.username=produser
启动时指定环境:
bash复制java -jar contact-system.jar --spring.profiles.active=prod
5.2 性能优化实践
- 二级缓存配置:
xml复制<cache eviction="LRU" flushInterval="60000"
size="512" readOnly="true"/>
- 连接池调优:
properties复制spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
6. 典型问题排查实录
6.1 中文乱码解决方案
必须确保全链路编码统一:
- 数据库连接字符串追加参数:
properties复制spring.datasource.url=jdbc:mysql://...?useUnicode=true&characterEncoding=UTF-8
- SpringMVC配置字符过滤器:
java复制@Bean
public FilterRegistrationBean<CharacterEncodingFilter> encodingFilter() {
FilterRegistrationBean<CharacterEncodingFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new CharacterEncodingFilter());
bean.addInitParameter("encoding", "UTF-8");
bean.addInitParameter("forceEncoding", "true");
bean.addUrlPatterns("/*");
return bean;
}
6.2 事务失效场景
常见陷阱及解决方案:
-
自调用问题:同一个类中方法A调用带@Transactional的方法B会导致事务失效。解决方案是将事务方法放到另一个Service中。
-
异常捕获不当:默认只对RuntimeException回滚。建议明确指定:
java复制@Transactional(rollbackFor = Exception.class)
- 数据库引擎选择:MyISAM不支持事务,必须使用InnoDB。
7. 安全加固措施
7.1 XSS防护方案
采用双重防御策略:
- 前端使用vue-sanitize等库过滤输入
- 后端使用Spring HtmlUtils:
java复制String safeName = HtmlUtils.htmlEscape(rawName);
7.2 接口权限控制
基于拦截器的实现:
java复制public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("X-Auth-Token");
if (!tokenService.validate(token)) {
response.sendError(403, "Access denied");
return false;
}
return true;
}
}
这个通讯录系统最终支持了200+用户的日常使用,峰值QPS达到150。开发过程中最大的收获是:SSM框架虽然传统,但在适当优化后完全能够支撑中小型企业的核心业务系统。特别是在事务控制和SQL优化方面,需要开发者深入理解框架原理而非简单使用。