南京作为六朝古都,拥有丰富的历史文化遗产和现代都市魅力,每年吸引大量游客前来观光。然而,旅游信息的碎片化分布给游客规划行程带来了不小的挑战。各类景点信息、住宿推荐、交通指南分散在不同平台,缺乏统一整合。针对这一痛点,我们开发了南京智慧旅游门户平台,通过集中化信息管理和智能化服务推荐,为游客提供一站式的旅游信息服务体验。
系统架构与技术栈
平台采用经典的JSP+Servlet三层架构,严格遵循MVC设计模式。前端使用HTML+CSS+JavaScript构建用户界面,结合JSP动态页面技术实现数据展示。后端基于Servlet处理业务逻辑,通过JDBC与MySQL数据库进行数据交互。这种架构确保了系统的高内聚低耦合,便于后续维护和功能扩展。
在技术选型上,JSP负责视图渲染,使用JSTL标签库和EL表达式替代传统的Scriptlet代码,提升了代码的可读性和维护性。Servlet作为控制器层,统一处理HTTP请求,进行参数验证和业务分发。数据库连接池的使用优化了系统性能,避免了频繁创建和销毁数据库连接的开销。
数据库设计亮点
旅游景点表设计分析
CREATE TABLE `lvyoujingdian` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`jingdianbianhao` varchar(50) DEFAULT '' COMMENT '景点编号',
`jingdianmingcheng` varchar(50) DEFAULT '' COMMENT '景点名称',
`jingdianzhutu` varchar(50) DEFAULT '' COMMENT '景点主图',
`suoshudiqu` varchar(50) DEFAULT '' COMMENT '所属地区',
`jingdianjieshao` mediumtext DEFAULT NULL COMMENT '景点介绍',
`menpiaojiage` varchar(50) DEFAULT '' COMMENT '门票价格',
`kaifangshijian` varchar(50) DEFAULT '' COMMENT '开放时间',
`issh` varchar(2) DEFAULT '否' COMMENT '是否审核',
`addtime` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '添加时间',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8 COMMENT='旅游景点表'
该表设计体现了良好的规范化思想。ID字段作为自增主键,确保每条记录的唯一性。jingdianbianhao采用业务编号机制,便于管理查询。mediumtext类型的jingdianjieshao字段支持详细的景点描述,满足富文本内容存储需求。issh审核状态字段和addtime时间戳为后台管理提供了有效的数据控制手段。
用户注册表设计优化
CREATE TABLE `yonghuzhuce` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`yonghuming` varchar(50) DEFAULT '' COMMENT '用户名',
`mima` varchar(50) DEFAULT '' COMMENT '密码',
`xingming` varchar(50) DEFAULT '' COMMENT '姓名',
`xingbie` varchar(2) DEFAULT '' COMMENT '性别',
`chushengnianyue` varchar(50) DEFAULT '' COMMENT '出生年月',
`QQ` varchar(50) DEFAULT '' COMMENT 'QQ',
`youxiang` varchar(50) DEFAULT '' COMMENT '邮箱',
`dianhua` varchar(50) DEFAULT '' COMMENT '电话',
`shenfenzheng` varchar(50) DEFAULT '' COMMENT '身份证',
`touxiang` varchar(50) DEFAULT '' COMMENT '头像',
`dizhi` varchar(300) DEFAULT '' COMMENT '地址',
`beizhu` varchar(500) DEFAULT '' COMMENT '备注',
`addtime` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '添加时间',
`issh` varchar(2) DEFAULT '否' COMMENT '是否审核',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COMMENT='用户注册表'
用户表设计考虑了完整的用户画像需求。各字段长度设置合理,如dizhi地址字段预留300字符空间,beizhu备注字段500字符,满足不同场景下的信息存储需求。身份证、电话、邮箱等敏感信息单独存储,便于后续的数据加密处理。审核状态机制确保了用户质量可控。

核心功能实现
景点信息管理模块
景点管理是平台的核心功能,实现了景点信息的增删改查完整流程。后端通过Servlet统一接收请求,调用相应的Service层处理业务逻辑。
景点查询Servlet核心代码:
@WebServlet("/jingdainQueryServlet")
public class JingdainQueryServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String jingdianmingcheng = request.getParameter("jingdianmingcheng");
String suoshudiqu = request.getParameter("suoshudiqu");
String currentPage = request.getParameter("currentPage");
JingdainService service = new JingdainService();
PageBean<Jingdain> pageBean = service.findByPage(jingdianmingcheng, suoshudiqu, currentPage);
request.setAttribute("pageBean", pageBean);
request.getRequestDispatcher("/admin/jingdain/list.jsp").forward(request, response);
}
}
分页查询业务逻辑:
public class JingdainService {
public PageBean<Jingdain> findByPage(String jingdianmingcheng, String suoshudiqu, String currentPageStr) {
Connection conn = null;
try {
int currentPage = Integer.parseInt(currentPageStr);
int pageSize = 10;
PageBean<Jingdain> pageBean = new PageBean<>();
pageBean.setCurrentPage(currentPage);
pageBean.setPageSize(pageSize);
conn = JDBCUtils.getConnection();
JingdainDao dao = new JingdainDao();
// 查询总记录数
int totalCount = dao.findTotalCount(conn, jingdianmingcheng, suoshudiqu);
pageBean.setTotalCount(totalCount);
// 查询当前页数据
int start = (currentPage - 1) * pageSize;
List<Jingdain> list = dao.findByPage(conn, jingdianmingcheng, suoshudiqu, start, pageSize);
pageBean.setList(list);
return pageBean;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
JDBCUtils.closeConnection(conn);
}
}
}

用户评论系统
评论系统采用异步提交方式,提升用户体验。前端通过AJAX技术实现无刷新提交,后端进行数据验证和存储。
评论提交Servlet:
@WebServlet("/pinglunAddServlet")
public class PinglunAddServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
String xinwenID = request.getParameter("xinwenID");
String pinglunneirong = request.getParameter("pinglunneirong");
String pinglunren = request.getParameter("pinglunren");
String pingfen = request.getParameter("pingfen");
String biao = request.getParameter("biao");
String jdmc = request.getParameter("jdmc");
// 数据验证
if (pinglunneirong == null || pinglunneirong.trim().isEmpty()) {
response.getWriter().write("error:评论内容不能为空");
return;
}
Pinglun pinglun = new Pinglun();
pinglun.setXinwenID(xinwenID);
pinglun.setPinglunneirong(pinglunneirong);
pinglun.setPinglunren(pinglunren);
pinglun.setPingfen(pingfen);
pinglun.setBiao(biao);
pinglun.setJdmc(jdmc);
PinglunService service = new PinglunService();
boolean result = service.addPinglun(pinglun);
response.setContentType("application/json;charset=utf-8");
if (result) {
response.getWriter().write("{\"status\":\"success\"}");
} else {
response.getWriter().write("{\"status\":\"error\"}");
}
}
}
评论数据访问层:
public class PinglunDao {
public boolean addPinglun(Connection conn, Pinglun pinglun) throws SQLException {
String sql = "INSERT INTO pinglun (xinwenID, pinglunneirong, pinglunren, pingfen, biao, jdmc) VALUES (?, ?, ?, ?, ?, ?)";
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, pinglun.getXinwenID());
pstmt.setString(2, pinglun.getPinglunneirong());
pstmt.setString(3, pinglun.getPinglunren());
pstmt.setString(4, pinglun.getPingfen());
pstmt.setString(5, pinglun.getBiao());
pstmt.setString(6, pinglun.getJdmc());
return pstmt.executeUpdate() > 0;
} finally {
if (pstmt != null) {
pstmt.close();
}
}
}
}

权限管理系统
平台采用基于角色的访问控制机制,通过管理员表实现多级权限管理。
登录验证Servlet:
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
String userType = request.getParameter("userType");
// 密码MD5加密验证
String encryptedPwd = MD5Util.encrypt(password);
if ("admin".equals(userType)) {
AdminService adminService = new AdminService();
Admin admin = adminService.login(username, encryptedPwd);
if (admin != null) {
HttpSession session = request.getSession();
session.setAttribute("admin", admin);
response.sendRedirect("admin/index.jsp");
} else {
request.setAttribute("login_error", "用户名或密码错误");
request.getRequestDispatcher("admin/login.jsp").forward(request, response);
}
} else {
UserService userService = new UserService();
User user = userService.login(username, encryptedPwd);
if (user != null) {
if ("是".equals(user.getIssh())) {
HttpSession session = request.getSession();
session.setAttribute("user", user);
response.sendRedirect("index.jsp");
} else {
request.setAttribute("login_error", "账号待审核,请联系管理员");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
} else {
request.setAttribute("login_error", "用户名或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
}
}
权限拦截过滤器:
@WebFilter("/*")
public class PermissionFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String uri = request.getRequestURI();
// 放行静态资源和登录页面
if (uri.contains("/css/") || uri.contains("/js/") || uri.contains("/images/") ||
uri.contains("/login.jsp") || uri.contains("/loginServlet")) {
chain.doFilter(req, resp);
return;
}
HttpSession session = request.getSession();
// 管理员权限验证
if (uri.contains("/admin/")) {
Admin admin = (Admin) session.getAttribute("admin");
if (admin == null) {
response.sendRedirect(request.getContextPath() + "/admin/login.jsp");
return;
}
}
// 用户权限验证
if (uri.contains("/user/")) {
User user = (User) session.getAttribute("user");
if (user == null) {
response.sendRedirect(request.getContextPath() + "/login.jsp");
return;
}
}
chain.doFilter(req, resp);
}
}

实体模型设计
平台采用面向对象的设计思想,为每个数据库表创建对应的实体类,封装业务属性和行为。
旅游景点实体类:
public class Lvyoujingdian {
private Integer ID;
private String jingdianbianhao;
private String jingdianmingcheng;
private String jingdianzhutu;
private String suoshudiqu;
private String jingdianjieshao;
private String menpiaojiage;
private String kaifangshijian;
private String issh;
private Timestamp addtime;
// 构造方法
public Lvyoujingdian() {}
public Lvyoujingdian(Integer ID, String jingdianbianhao, String jingdianmingcheng,
String jingdianzhutu, String suoshudiqu, String jingdianjieshao,
String menpiaojiage, String kaifangshijian, String issh, Timestamp addtime) {
this.ID = ID;
this.jingdianbianhao = jingdianbianhao;
this.jingdianmingcheng = jingdianmingcheng;
this.jingdianzhutu = jingdianzhutu;
this.suoshudiqu = suoshudiqu;
this.jingdianjieshao = jingdianjieshao;
this.menpiaojiage = menpiaojiage;
this.kaifangshijian = kaifangshijian;
this.issh = issh;
this.addtime = addtime;
}
// Getter和Setter方法
public Integer getID() { return ID; }
public void setID(Integer ID) { this.ID = ID; }
public String getJingdianbianhao() { return jingdianbianhao; }
public void setJingdianbianhao(String jingdianbianhao) { this.jingdianbianhao = jingdianbianhao; }
// 其他getter/setter方法...
// 业务方法
public boolean isAudited() {
return "是".equals(this.issh);
}
public String getPriceDisplay() {
if ("免费".equals(this.menpiaojiage) || this.menpiaojiage == null) {
return "免费开放";
} else {
return "¥" + this.menpiaojiage;
}
}
}
用户注册实体类:
public class Yonghuzhuce {
private Integer ID;
private String yonghuming;
private String mima;
private String xingming;
private String xingbie;
private String chushengnianyue;
private String QQ;
private String youxiang;
private String dianhua;
private String shenfenzheng;
private String touxiang;
private String dizhi;
private String beizhu;
private Timestamp addtime;
private String issh;
// 构造方法和getter/setter...
public boolean isAdult() {
if (chushengnianyue != null && chushengnianyue.length() >= 4) {
int birthYear = Integer.parseInt(chushengnianyue.substring(0, 4));
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
return (currentYear - birthYear) >= 18;
}
return false;
}
public String getMaskedIdentity() {
if (shenfenzheng != null && shenfenzheng.length() > 10) {
return shenfenzheng.substring(0, 6) + "****" + shenfenzheng.substring(14);
}
return shenfenzheng;
}
}

功能展望与优化
智能化推荐引擎
当前平台主要提供基础的信息查询功能,未来可引入机器学习算法构建个性化推荐系统。基于用户的历史浏览记录、收藏行为和评论内容,使用协同过滤算法为用户推荐可能感兴趣的景点和旅游路线。
实现思路:
- 收集用户行为数据,建立用户画像
- 使用Apache Mahout或Spark MLlib实现推荐算法
- 通过Redis缓存热门推荐结果,提升响应速度
微服务架构改造
随着业务规模扩大,可将单体应用拆分为微服务架构。将用户服务、景点服务、评论服务等独立部署,提高系统的可扩展性和容错能力。
架构规划:
// 用户服务接口示例
@FeignClient(name = "user-service")
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Integer id);
@PostMapping("/users")
ResponseEntity<User> createUser(@RequestBody User user);
}
// 景点服务接口示例
@FeignClient(name = "attraction-service")
public interface AttractionServiceClient {
@GetMapping("/attractions")
Page<Attraction> getAttractions(@RequestParam Map<String, String> params);
}
移动端适配优化
开发响应式前端界面,