├── .gitignore ├── README.md ├── beauty_ssm.iml ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── yingjun │ │ └── ssm │ │ ├── aop │ │ └── BindingResultAop.java │ │ ├── cache │ │ ├── RedisCache.java │ │ └── RedisClusterCache.java │ │ ├── dao │ │ ├── GoodsDao.java │ │ ├── OrderDao.java │ │ └── UserDao.java │ │ ├── dto │ │ └── BaseResult.java │ │ ├── entity │ │ ├── Goods.java │ │ ├── Order.java │ │ └── User.java │ │ ├── enums │ │ └── ResultEnum.java │ │ ├── exception │ │ ├── BizException.java │ │ └── GlobalExceptionResolver.java │ │ ├── quartz │ │ └── BizQuartz.java │ │ ├── service │ │ ├── GoodsService.java │ │ ├── UserService.java │ │ └── impl │ │ │ ├── GoodsServiceImpl.java │ │ │ └── UserServiceImpl.java │ │ ├── util │ │ ├── CustomDateSerializer.java │ │ ├── ProtoStuffSerializerUtil.java │ │ └── TimeUtils.java │ │ ├── validator │ │ ├── CustomDateSerializer.java │ │ ├── Not999.java │ │ └── Not999Validator.java │ │ └── web │ │ ├── GoodsController.java │ │ └── UserController.java ├── resources │ ├── ValidationMessages.properties │ ├── jdbc.properties │ ├── log4j.properties │ ├── mapper │ │ ├── GoodsDao.xml │ │ ├── OrderDao.xml │ │ └── UserDao.xml │ ├── mybatis-config.xml │ ├── redis.properties │ ├── spring │ │ ├── spring-dao.xml │ │ ├── spring-quartz.xml │ │ ├── spring-redis.xml │ │ ├── spring-service.xml │ │ └── spring-web.xml │ └── sql │ │ ├── execute_bug.sql │ │ └── schema.sql └── webapp │ ├── WEB-INF │ ├── jsp │ │ ├── common │ │ │ ├── head.jsp │ │ │ └── tag.jsp │ │ ├── goodslist.jsp │ │ └── userlist.jsp │ └── web.xml │ ├── index.jsp │ └── resource │ └── script │ └── handler.js └── test └── java └── com └── yingjun └── ssm └── dao ├── GoodsDaoTest.java ├── OrderDaoTest.java └── UserDaoTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /catalina.base_IS_UNDEFINED/ 3 | /.settings/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #优雅的SSM架构(Spring+SpringMVC+Mybatis) 2 | - Maven 3 | - Spring(IOC DI AOP 声明式事务处理) 4 | - SpringMVC(支持Restful风格) 5 | - Hibernate Validate(参数校验) 6 | - Mybatis(最少配置方案) 7 | - Quartz时间调度 8 | - Redis缓存(ProtoStuff序列化) 9 | - [Redis Sentinel主从高可用方案](http://wosyingjun.iteye.com/blog/2289593) 10 | - [Redis Cluster集群高可用方案](http://wosyingjun.iteye.com/blog/2289220) 11 | - [Druid(数据源配置 sql防注入 sql性能监控)](http://wosyingjun.iteye.com/blog/2306139) 12 | - 统一的异常处理 13 | - JSP JSTL JavaScript 14 | - Sping Shiro权限控制(待完善) 15 | 16 | ###**架构图:** 17 | ![](http://i.imgur.com/vc6iu0X.png) -------------------------------------------------------------------------------- /beauty_ssm.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.yingjun.ssm 5 | beauty_ssm 6 | war 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | UTF-8 11 | yyyyMMdd 12 | 4.2.0.RELEASE 13 | 14 | 15 | 16 | 17 | 18 | com.google.guava 19 | guava 20 | 18.0 21 | 22 | 23 | 24 | log4j 25 | log4j 26 | 1.2.17 27 | 28 | 29 | org.slf4j 30 | slf4j-api 31 | 1.7.5 32 | 33 | 34 | org.slf4j 35 | slf4j-log4j12 36 | 1.7.5 37 | 38 | 39 | 40 | org.apache.commons 41 | commons-lang3 42 | 3.3.2 43 | 44 | 45 | 46 | commons-io 47 | commons-io 48 | 2.4 49 | 50 | 51 | 52 | junit 53 | junit 54 | 4.12 55 | 56 | 57 | 58 | commons-collections 59 | commons-collections 60 | 3.2.1 61 | 62 | 63 | 64 | org.quartz-scheduler 65 | quartz 66 | 1.8.5 67 | 68 | 69 | 70 | com.alibaba 71 | fastjson 72 | 1.2.8 73 | 74 | 75 | 76 | org.hibernate 77 | hibernate-validator 78 | 4.2.0.Final 79 | 80 | 81 | 82 | 83 | 84 | org.springframework 85 | spring-core 86 | ${spring.version} 87 | 88 | 89 | 90 | org.springframework 91 | spring-beans 92 | ${spring.version} 93 | 94 | 95 | 96 | org.springframework 97 | spring-context 98 | ${spring.version} 99 | 100 | 101 | 102 | org.springframework 103 | spring-context-support 104 | ${spring.version} 105 | 106 | 107 | 108 | org.springframework 109 | spring-aop 110 | ${spring.version} 111 | 112 | 113 | 114 | org.springframework 115 | spring-aspects 116 | ${spring.version} 117 | 118 | 119 | 120 | org.springframework 121 | spring-expression 122 | ${spring.version} 123 | 124 | 125 | 126 | org.springframework 127 | spring-tx 128 | ${spring.version} 129 | 130 | 131 | 132 | org.springframework 133 | spring-test 134 | ${spring.version} 135 | 136 | 137 | 138 | 139 | 140 | jstl 141 | jstl 142 | 1.2 143 | 144 | 145 | 146 | javax.servlet 147 | javax.servlet-api 148 | 3.1.0 149 | 150 | 151 | 152 | taglibs 153 | standard 154 | 1.1.2 155 | 156 | 157 | 158 | org.springframework 159 | spring-web 160 | ${spring.version} 161 | 162 | 163 | 164 | org.springframework 165 | spring-webmvc 166 | ${spring.version} 167 | 168 | 169 | 170 | com.fasterxml.jackson.core 171 | jackson-databind 172 | 2.7.4 173 | 174 | 175 | 176 | 177 | 178 | mysql 179 | mysql-connector-java 180 | 5.1.38 181 | runtime 182 | 183 | 184 | 185 | com.alibaba 186 | druid 187 | 1.0.20 188 | 189 | 190 | 191 | org.springframework 192 | spring-jdbc 193 | ${spring.version} 194 | 195 | 196 | 197 | org.mybatis 198 | mybatis 199 | 3.4.0 200 | 201 | 202 | 203 | org.mybatis 204 | mybatis-spring 205 | 1.3.0 206 | 207 | 208 | 209 | redis.clients 210 | jedis 211 | 2.8.0 212 | 213 | 214 | org.springframework.data 215 | spring-data-redis 216 | 1.6.4.RELEASE 217 | 218 | 219 | 220 | com.dyuproject.protostuff 221 | protostuff-core 222 | 1.0.8 223 | 224 | 225 | 226 | com.dyuproject.protostuff 227 | protostuff-runtime 228 | 1.0.8 229 | 230 | 231 | 232 | 233 | 234 | 235 | ${project.artifactId}_${project.version}_${maven.build.timestamp} 236 | 237 | 238 | 239 | src/main/resources 240 | 241 | true 242 | 243 | 244 | 245 | 246 | maven-compiler-plugin 247 | 2.3.2 248 | 249 | 1.7 250 | 1.7 251 | 252 | 253 | 254 | org.apache.maven.plugins 255 | maven-war-plugin 256 | 2.6 257 | 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/aop/BindingResultAop.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.aop; 2 | 3 | import com.yingjun.ssm.dto.BaseResult; 4 | import org.aspectj.lang.ProceedingJoinPoint; 5 | import org.aspectj.lang.annotation.Around; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.aspectj.lang.annotation.Pointcut; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.validation.BindingResult; 12 | 13 | /** 14 | * @author yingjun 15 | * 16 | * 采用AOP的方式处理参数问题。 17 | */ 18 | @Component 19 | @Aspect 20 | public class BindingResultAop { 21 | 22 | private final Logger LOG = LoggerFactory.getLogger(this.getClass()); 23 | 24 | @Pointcut("execution(* com.yingjun.ssm.web.*.*(..))") 25 | public void aopMethod(){} 26 | 27 | @Around("aopMethod()") 28 | public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ 29 | LOG.info("before method invoking!"); 30 | BindingResult bindingResult = null; 31 | for(Object arg:joinPoint.getArgs()){ 32 | if(arg instanceof BindingResult){ 33 | bindingResult = (BindingResult) arg; 34 | } 35 | } 36 | if(bindingResult != null){ 37 | if(bindingResult.hasErrors()){ 38 | String errorInfo="["+bindingResult.getFieldError().getField()+"]"+bindingResult.getFieldError().getDefaultMessage(); 39 | return new BaseResult(false, errorInfo); 40 | } 41 | } 42 | return joinPoint.proceed(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/cache/RedisCache.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.cache; 2 | 3 | import com.yingjun.ssm.util.ProtoStuffSerializerUtil; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.dao.DataAccessException; 6 | import org.springframework.data.redis.connection.RedisConnection; 7 | import org.springframework.data.redis.core.RedisCallback; 8 | import org.springframework.data.redis.core.RedisTemplate; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | /** 15 | * redis缓存 16 | * 17 | * @author yingjun 18 | * 19 | */ 20 | @Component 21 | public class RedisCache { 22 | 23 | 24 | public final static String CAHCENAME="cache";//缓存名 25 | public final static int CAHCETIME=60;//默认缓存时间 26 | 27 | @Autowired 28 | private RedisTemplate redisTemplate; 29 | 30 | public boolean putCache(String key, T obj) { 31 | final byte[] bkey = key.getBytes(); 32 | final byte[] bvalue = ProtoStuffSerializerUtil.serialize(obj); 33 | boolean result = redisTemplate.execute(new RedisCallback() { 34 | @Override 35 | public Boolean doInRedis(RedisConnection connection) throws DataAccessException { 36 | return connection.setNX(bkey, bvalue); 37 | } 38 | }); 39 | return result; 40 | } 41 | 42 | public void putCacheWithExpireTime(String key, T obj, final long expireTime) { 43 | final byte[] bkey = key.getBytes(); 44 | final byte[] bvalue = ProtoStuffSerializerUtil.serialize(obj); 45 | redisTemplate.execute(new RedisCallback() { 46 | @Override 47 | public Boolean doInRedis(RedisConnection connection) throws DataAccessException { 48 | connection.setEx(bkey, expireTime, bvalue); 49 | return true; 50 | } 51 | }); 52 | } 53 | 54 | public boolean putListCache(String key, List objList) { 55 | final byte[] bkey = key.getBytes(); 56 | final byte[] bvalue = ProtoStuffSerializerUtil.serializeList(objList); 57 | boolean result = redisTemplate.execute(new RedisCallback() { 58 | @Override 59 | public Boolean doInRedis(RedisConnection connection) throws DataAccessException { 60 | return connection.setNX(bkey, bvalue); 61 | } 62 | }); 63 | return result; 64 | } 65 | 66 | public boolean putListCacheWithExpireTime(String key, List objList, final long expireTime) { 67 | final byte[] bkey = key.getBytes(); 68 | final byte[] bvalue = ProtoStuffSerializerUtil.serializeList(objList); 69 | boolean result = redisTemplate.execute(new RedisCallback() { 70 | @Override 71 | public Boolean doInRedis(RedisConnection connection) throws DataAccessException { 72 | connection.setEx(bkey, expireTime, bvalue); 73 | return true; 74 | } 75 | }); 76 | return result; 77 | } 78 | 79 | public T getCache(final String key, Class targetClass) { 80 | byte[] result = redisTemplate.execute(new RedisCallback() { 81 | @Override 82 | public byte[] doInRedis(RedisConnection connection) throws DataAccessException { 83 | return connection.get(key.getBytes()); 84 | } 85 | }); 86 | if (result == null) { 87 | return null; 88 | } 89 | return ProtoStuffSerializerUtil.deserialize(result, targetClass); 90 | } 91 | 92 | public List getListCache(final String key, Class targetClass) { 93 | byte[] result = redisTemplate.execute(new RedisCallback() { 94 | @Override 95 | public byte[] doInRedis(RedisConnection connection) throws DataAccessException { 96 | return connection.get(key.getBytes()); 97 | } 98 | }); 99 | if (result == null) { 100 | return null; 101 | } 102 | return ProtoStuffSerializerUtil.deserializeList(result, targetClass); 103 | } 104 | 105 | /** 106 | * 精确删除key 107 | * 108 | * @param key 109 | */ 110 | public void deleteCache(String key) { 111 | redisTemplate.delete(key); 112 | } 113 | 114 | /** 115 | * 模糊删除key 116 | * 117 | * @param pattern 118 | */ 119 | public void deleteCacheWithPattern(String pattern) { 120 | Set keys = redisTemplate.keys(pattern); 121 | redisTemplate.delete(keys); 122 | } 123 | 124 | /** 125 | * 清空所有缓存 126 | */ 127 | public void clearCache() { 128 | deleteCacheWithPattern(RedisCache.CAHCENAME+"|*"); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/cache/RedisClusterCache.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.cache; 2 | 3 | import com.yingjun.ssm.util.ProtoStuffSerializerUtil; 4 | import org.springframework.stereotype.Component; 5 | import redis.clients.jedis.Jedis; 6 | import redis.clients.jedis.JedisCluster; 7 | import redis.clients.jedis.JedisPool; 8 | 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | 15 | /** 16 | * redis缓存 17 | * 18 | * 采用Jedis Cluster 19 | * 20 | * @author yingjun 21 | * 22 | */ 23 | @Component 24 | public class RedisClusterCache { 25 | 26 | 27 | public final static String CAHCENAME="cache";//缓存名 28 | public final static int CAHCETIME=60;//默认缓存时间 29 | 30 | //@Autowired 31 | private JedisCluster jedisCluster; 32 | 33 | 34 | public void putCache(String key, T obj) { 35 | final byte[] bkey = key.getBytes(); 36 | final byte[] bvalue = ProtoStuffSerializerUtil.serialize(obj); 37 | jedisCluster.set(bkey,bvalue); 38 | } 39 | 40 | public void putCacheWithExpireTime(String key, T obj, int expireTime) { 41 | final byte[] bkey = key.getBytes(); 42 | final byte[] bvalue = ProtoStuffSerializerUtil.serialize(obj); 43 | jedisCluster.setex(bkey, expireTime, bvalue); 44 | } 45 | 46 | public void putListCache(String key, List objList) { 47 | final byte[] bkey = key.getBytes(); 48 | final byte[] bvalue = ProtoStuffSerializerUtil.serializeList(objList); 49 | jedisCluster.set(bkey,bvalue); 50 | } 51 | 52 | public void putListCacheWithExpireTime(String key, List objList, int expireTime) { 53 | final byte[] bkey = key.getBytes(); 54 | final byte[] bvalue = ProtoStuffSerializerUtil.serializeList(objList); 55 | jedisCluster.setex(bkey, expireTime, bvalue); 56 | } 57 | 58 | public T getCache(final String key, Class targetClass) { 59 | byte[] result =jedisCluster.get(key.getBytes()); 60 | if (result == null) { 61 | return null; 62 | } 63 | return ProtoStuffSerializerUtil.deserialize(result, targetClass); 64 | } 65 | 66 | public List getListCache(String key, Class targetClass) { 67 | byte[] result =jedisCluster.get(key.getBytes()); 68 | if (result == null) { 69 | return null; 70 | } 71 | return ProtoStuffSerializerUtil.deserializeList(result, targetClass); 72 | } 73 | 74 | /** 75 | * 精确删除key 76 | * 77 | * @param key 78 | */ 79 | public void deleteCache(String key) { 80 | jedisCluster.del(key); 81 | } 82 | 83 | /** 84 | * 模糊删除key 85 | * 86 | * @param pattern 87 | */ 88 | public void deleteCacheWithPattern(String pattern) { 89 | Set keys =this.keys(pattern); 90 | for(String key:keys){ 91 | jedisCluster.del(key); 92 | } 93 | } 94 | 95 | /** 96 | * 清空所有缓存 97 | */ 98 | public void clearCache() { 99 | deleteCacheWithPattern(RedisClusterCache.CAHCENAME+"|*"); 100 | } 101 | 102 | /** 103 | * 由于JedisCluster没有提供对keys命令的封装,只能自己实现 104 | * @param pattern 105 | * @return 106 | */ 107 | public Set keys(String pattern){ 108 | Set keys = new HashSet<>(); 109 | Map clusterNodes = jedisCluster.getClusterNodes(); 110 | for(String k : clusterNodes.keySet()){ 111 | JedisPool jp = clusterNodes.get(k); 112 | Jedis connection = jp.getResource(); 113 | try { 114 | keys.addAll(connection.keys(pattern)); 115 | } catch(Exception e){ 116 | e.printStackTrace(); 117 | } finally{ 118 | //用完一定要close这个链接!!! 119 | connection.close(); 120 | } 121 | } 122 | return keys; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/dao/GoodsDao.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.dao; 2 | 3 | import com.yingjun.ssm.entity.Goods; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public interface GoodsDao { 10 | /** 11 | * 根据偏移量查询可用商品列表 12 | * 13 | * @param offset 14 | * @param limit 15 | * @return 16 | */ 17 | List queryAll(@Param("offset") int offset, @Param("limit") int limit); 18 | 19 | /** 20 | * 商品减库存 21 | * 22 | * @param goodsId 23 | * @return 如果更新行数大于1,表示更新的行数 24 | */ 25 | int reduceNumber(long goodsId); 26 | 27 | /** 28 | * 使用存储过程执行抢购 29 | * 30 | * 能提升并发性的原因: 31 | * 1、减少多个sql语句执行来回的网络延时。 32 | * 2、通过mysql自身的事物提升效率。 33 | * 34 | * @param paramMap 35 | */ 36 | void bugWithProcedure(Map paramMap); 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/dao/OrderDao.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.dao; 2 | 3 | import com.yingjun.ssm.entity.Order; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | import java.util.List; 7 | 8 | public interface OrderDao { 9 | 10 | /** 11 | * 插入订单明细 12 | * 13 | * @param userId 14 | * @param goodsId 15 | * @return 16 | */ 17 | int insertOrder(@Param("userId") long userId,@Param("goodsId") long goodsId, @Param("title")String title); 18 | 19 | /** 20 | * 根据用户手机号查询订单 21 | * 22 | * @param userPhone 23 | * @return 24 | */ 25 | List queryByUserPhone(@Param("userPhone") long userPhone); 26 | 27 | 28 | /** 29 | * 根据偏移量查询订单列表 30 | * @param offset 31 | * @param limit 32 | * @return 33 | */ 34 | List queryAll(@Param("offset") int offset, @Param("limit") int limit); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/dao/UserDao.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.dao; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import com.yingjun.ssm.entity.User; 8 | 9 | public interface UserDao { 10 | 11 | /** 12 | * 根据手机号查询用户对象 13 | * 14 | * @param userPhone 15 | * @return 16 | */ 17 | User queryByPhone(long userPhone); 18 | 19 | 20 | /** 21 | * 根据偏移量查询用户列表 22 | * 23 | * @param offset 24 | * @param limit 25 | * @return 26 | */ 27 | List queryAll(@Param("offset") int offset, @Param("limit") int limit); 28 | 29 | 30 | /** 31 | * 增加积分 32 | */ 33 | void addScore(@Param("add")int add); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/dto/BaseResult.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 9 | * @author yingjun 10 | * 11 | * ajax 请求的返回类型封装JSON结果 12 | */ 13 | @JsonInclude(JsonInclude.Include.NON_NULL) 14 | public class BaseResult implements Serializable { 15 | 16 | 17 | private static final long serialVersionUID = -4185151304730685014L; 18 | 19 | private boolean success; 20 | 21 | private T data; 22 | 23 | private String error; 24 | 25 | public BaseResult(boolean success, String error) { 26 | this.success = success; 27 | this.error = error; 28 | } 29 | 30 | public BaseResult(boolean success, T data) { 31 | this.success = success; 32 | this.data = data; 33 | } 34 | 35 | public boolean isSuccess() { 36 | return success; 37 | } 38 | 39 | public void setSuccess(boolean success) { 40 | this.success = success; 41 | } 42 | 43 | public T getData() { 44 | return data; 45 | } 46 | 47 | public void setData(T data) { 48 | this.data = data; 49 | } 50 | 51 | public String getError() { 52 | return error; 53 | } 54 | 55 | public void setError(String error) { 56 | this.error = error; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "BaseResult [success=" + success + ", data=" + data + ", error=" + error + "]"; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/entity/Goods.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 5 | import com.yingjun.ssm.util.CustomDateSerializer; 6 | import com.yingjun.ssm.validator.Not999; 7 | 8 | import javax.validation.constraints.Min; 9 | import java.util.Date; 10 | 11 | public class Goods { 12 | 13 | @Min(900) 14 | @Not999 //这个为自定义的验证标签 15 | private long goodsId; 16 | 17 | private String title; 18 | 19 | private float price; 20 | 21 | private short state;//0表示下架 1表示正常 22 | 23 | private int number; 24 | 25 | //这里展示了jackson封装好的以及自定义的对时间格式的转换方式 26 | //后续对于一些复杂的转换可以自定义转换方式 27 | @JsonFormat(pattern = "yyyy-MM-dd") 28 | private Date createTime; 29 | 30 | @JsonSerialize(using = CustomDateSerializer.class) 31 | private Date updateTime; 32 | 33 | public long getGoodsId() { 34 | return goodsId; 35 | } 36 | public void setGoodsId(long goodsId) { 37 | this.goodsId = goodsId; 38 | } 39 | public String getTitle() { 40 | return title; 41 | } 42 | public void setTitle(String title) { 43 | this.title = title; 44 | } 45 | public float getPrice() { 46 | return price; 47 | } 48 | public void setPrice(float price) { 49 | this.price = price; 50 | } 51 | 52 | public Date getCreateTime() { 53 | return createTime; 54 | } 55 | public void setCreateTime(Date createTime) { 56 | this.createTime = createTime; 57 | } 58 | public Date getUpdateTime() { 59 | return updateTime; 60 | } 61 | public void setUpdateTime(Date updateTime) { 62 | this.updateTime = updateTime; 63 | } 64 | public short getState() { 65 | return state; 66 | } 67 | public void setState(short state) { 68 | this.state = state; 69 | } 70 | 71 | public int getNumber() { 72 | return number; 73 | } 74 | public void setNumber(int number) { 75 | this.number = number; 76 | } 77 | @Override 78 | public String toString() { 79 | return "Goods [goodsId=" + goodsId + ", title=" + title + ", price=" + price + ", state=" + state + ", number=" + number + ", createTime=" 80 | + createTime + ", updateTime=" + updateTime + "]"; 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/entity/Order.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.entity; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * 订单 7 | * @author yingjun 8 | * 9 | */ 10 | public class Order { 11 | 12 | private long orderId; 13 | 14 | private User user; 15 | 16 | private long goodsId; 17 | 18 | private String title; 19 | 20 | private Date createTime; 21 | 22 | 23 | public User getUser() { 24 | return user; 25 | } 26 | 27 | 28 | public void setUser(User user) { 29 | this.user = user; 30 | } 31 | 32 | 33 | public long getOrderId() { 34 | return orderId; 35 | } 36 | 37 | 38 | public void setOrderId(long orderId) { 39 | this.orderId = orderId; 40 | } 41 | 42 | 43 | public String getTitle() { 44 | return title; 45 | } 46 | 47 | 48 | public void setTitle(String title) { 49 | this.title = title; 50 | } 51 | 52 | 53 | public Date getCreateTime() { 54 | return createTime; 55 | } 56 | 57 | 58 | public void setCreateTime(Date createTime) { 59 | this.createTime = createTime; 60 | } 61 | 62 | 63 | public long getGoodsId() { 64 | return goodsId; 65 | } 66 | 67 | 68 | public void setGoodsId(long goodsId) { 69 | this.goodsId = goodsId; 70 | } 71 | 72 | 73 | @Override 74 | public String toString() { 75 | return "Order [user=" + user + ", orderId=" + orderId + ", goodsId=" + goodsId + ", title=" + title + ", createTime=" + createTime + "]"; 76 | } 77 | 78 | 79 | 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.entity; 2 | 3 | import java.util.Date; 4 | 5 | 6 | /** 7 | * 用户 8 | * @author yingjun 9 | * 10 | */ 11 | public class User { 12 | 13 | private long userId; 14 | 15 | private String userName; 16 | 17 | private long userPhone; 18 | 19 | private Date createTime; 20 | 21 | private int score; 22 | 23 | public long getUserId() { 24 | return userId; 25 | } 26 | 27 | public void setUserId(long userId) { 28 | this.userId = userId; 29 | } 30 | 31 | 32 | public String getUserName() { 33 | return userName; 34 | } 35 | 36 | public void setUserName(String userName) { 37 | this.userName = userName; 38 | } 39 | 40 | public long getUserPhone() { 41 | return userPhone; 42 | } 43 | 44 | public void setUserPhone(long userPhone) { 45 | this.userPhone = userPhone; 46 | } 47 | 48 | public Date getCreateTime() { 49 | return createTime; 50 | } 51 | 52 | public void setCreateTime(Date createTime) { 53 | this.createTime = createTime; 54 | } 55 | 56 | public int getScore() { 57 | return score; 58 | } 59 | 60 | public void setScore(int score) { 61 | this.score = score; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "User [userId=" + userId + ", userName=" + userName + ", userPhone=" + userPhone + ", createTime=" + createTime + ", score=" + score 67 | + "]"; 68 | } 69 | 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/enums/ResultEnum.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.enums; 2 | 3 | /** 4 | * 业务异常基类,所有业务异常都必须继承于此异常 定义异常时,需要先确定异常所属模块。 例如:无效用户可以定义为 [10010001] 5 | * 前四位数为系统模块编号,后4位为错误代码 ,唯一。 6 | * 7 | * @author yingjun 8 | * 9 | */ 10 | public enum ResultEnum { 11 | 12 | // 数据库想操作异常 13 | DB_INSERT_RESULT_ERROR(99990001, "db insert error"), 14 | DB_UPDATE_RESULT_ERROR(99990002, "db update error"), 15 | DB_SELECTONE_IS_NULL(99990003,"db select return null"), 16 | 17 | // 系统异常 18 | INNER_ERROR(99980001, "系统错误"), 19 | TOKEN_IS_ILLICIT(99980002, "Token验证非法"), 20 | SESSION_IS_OUT_TIME(99980003, "会话超时"), 21 | 22 | // 用户相关异常 23 | INVALID_USER(1001001, "无效用户"); 24 | 25 | private int state; 26 | 27 | private String msg; 28 | 29 | ResultEnum(int state, String msg) { 30 | this.state = state; 31 | this.msg = msg; 32 | } 33 | 34 | public int getState() { 35 | return state; 36 | } 37 | 38 | public String getMsg() { 39 | return msg; 40 | } 41 | 42 | public static ResultEnum stateOf(int index) { 43 | for (ResultEnum state : values()) { 44 | if (state.getState() == index) { 45 | return state; 46 | } 47 | } 48 | return null; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/exception/BizException.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.exception; 2 | /** 3 | * 4 | * @author yingjun 5 | * 6 | */ 7 | public class BizException extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 1L; 10 | 11 | public BizException(String message) { 12 | super(message); 13 | } 14 | 15 | public BizException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/exception/GlobalExceptionResolver.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.exception; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.yingjun.ssm.dto.BaseResult; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | import org.springframework.web.servlet.HandlerExceptionResolver; 10 | import org.springframework.web.servlet.ModelAndView; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.io.PrintWriter; 15 | 16 | /** 17 | * 错误信息统一处理 18 | * 对未处理的错误信息做一个统一处理 19 | * @author yingjun 20 | * 21 | */ 22 | @Component 23 | public class GlobalExceptionResolver implements HandlerExceptionResolver { 24 | 25 | private final Logger LOG = LoggerFactory.getLogger(this.getClass()); 26 | 27 | @ResponseBody 28 | public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { 29 | LOG.error("访问" + request.getRequestURI() + " 发生错误, 错误信息:" + ex.getMessage()); 30 | //这里有2种选择 31 | //跳转到定制化的错误页面 32 | /*ModelAndView error = new ModelAndView("error"); 33 | error.addObject("exMsg", ex.getMessage()); 34 | error.addObject("exType", ex.getClass().getSimpleName().replace("\"", "'"));*/ 35 | //返回json格式的错误信息 36 | try { 37 | PrintWriter writer = response.getWriter(); 38 | BaseResult result=new BaseResult(false, ex.getMessage()); 39 | writer.write(JSON.toJSONString(result)); 40 | writer.flush(); 41 | } catch (Exception e) { 42 | LOG.error("Exception:",e); 43 | } 44 | return null; 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/quartz/BizQuartz.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.quartz; 2 | 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.scheduling.annotation.Scheduled; 8 | import org.springframework.stereotype.Component; 9 | 10 | import com.yingjun.ssm.cache.RedisCache; 11 | import com.yingjun.ssm.dao.UserDao; 12 | 13 | /** 14 | * 业务相关的作业调度 15 | * 16 | 字段 允许值 允许的特殊字符 17 | 秒 0-59 , - * / 18 | 分 0-59 , - * / 19 | 小时 0-23 , - * / 20 | 日期 1-31 , - * ? / L W C 21 | 月份 1-12 或者 JAN-DEC , - * / 22 | 星期 1-7 或者 SUN-SAT , - * ? / L C # 23 | 年(可选) 留空, 1970-2099 , - * / 24 | 25 | * 字符代表所有可能的值 26 | / 字符用来指定数值的增量 27 | L 字符仅被用于天(月)和天(星期)两个子表达式,表示一个月的最后一天或者一个星期的最后一天 28 | 6L 可以表示倒数第6天 29 | 30 | * @author yingjun 31 | * 32 | */ 33 | @Component 34 | public class BizQuartz { 35 | 36 | private final Logger LOG = LoggerFactory.getLogger(this.getClass()); 37 | @Autowired 38 | private UserDao userDao; 39 | @Autowired 40 | private RedisCache cache; 41 | 42 | /** 43 | * 用户自动加积分 44 | * 每天9点到17点每过1分钟所有用户加一次积分 45 | */ 46 | @Scheduled(cron = "0 0/1 9-17 * * ? ") 47 | public void addUserScore() { 48 | LOG.info("@Scheduled--------addUserScore()"); 49 | userDao.addScore(10); 50 | } 51 | /** 52 | * 每隔5分钟定时清理缓存 53 | */ 54 | @Scheduled(cron = "0 0/5 * * * ? ") 55 | public void cacheClear() { 56 | LOG.info("@Scheduled-------cacheClear()"); 57 | cache.clearCache(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/service/GoodsService.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.service; 2 | 3 | import com.yingjun.ssm.entity.Goods; 4 | 5 | import java.util.List; 6 | 7 | public interface GoodsService { 8 | 9 | /** 10 | * 根据偏移量查询可用商品列表 11 | * 12 | * @param offset 13 | * @param limit 14 | * @return 15 | */ 16 | List getGoodsList(int offset, int limit); 17 | 18 | /** 19 | * 商品购买 20 | * 21 | * @param userPhone 22 | * @param goodsId 23 | * @param useProcedure 24 | * 是否用存储过程提高并发能力 25 | */ 26 | 27 | void buyGoods(long userPhone, long goodsId, boolean useProcedure); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.service; 2 | 3 | import java.util.List; 4 | 5 | import com.yingjun.ssm.entity.User; 6 | 7 | public interface UserService { 8 | 9 | List getUserList(int offset, int limit); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/service/impl/GoodsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.service.impl; 2 | 3 | import com.yingjun.ssm.cache.RedisCache; 4 | import com.yingjun.ssm.dao.GoodsDao; 5 | import com.yingjun.ssm.dao.OrderDao; 6 | import com.yingjun.ssm.dao.UserDao; 7 | import com.yingjun.ssm.entity.Goods; 8 | import com.yingjun.ssm.entity.User; 9 | import com.yingjun.ssm.enums.ResultEnum; 10 | import com.yingjun.ssm.exception.BizException; 11 | import com.yingjun.ssm.service.GoodsService; 12 | import org.apache.commons.collections.MapUtils; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | @Service 24 | public class GoodsServiceImpl implements GoodsService { 25 | 26 | private final Logger LOG = LoggerFactory.getLogger(this.getClass()); 27 | @Autowired 28 | private GoodsDao goodsDao; 29 | @Autowired 30 | private OrderDao orderDao; 31 | @Autowired 32 | private UserDao userDao; 33 | @Autowired 34 | private RedisCache cache; 35 | 36 | @Override 37 | public List getGoodsList(int offset, int limit) { 38 | String cache_key = RedisCache.CAHCENAME + "|getGoodsList|" + offset + "|" + limit; 39 | List result_cache = cache.getListCache(cache_key, Goods.class); 40 | if (result_cache != null) { 41 | LOG.info("get cache with key:" + cache_key); 42 | } else { 43 | // 缓存中没有再去数据库取,并插入缓存(缓存时间为60秒) 44 | result_cache = goodsDao.queryAll(offset, limit); 45 | cache.putListCacheWithExpireTime(cache_key, result_cache, RedisCache.CAHCETIME); 46 | LOG.info("put cache with key:" + cache_key); 47 | return result_cache; 48 | } 49 | return result_cache; 50 | } 51 | 52 | @Transactional 53 | @Override 54 | public void buyGoods(long userPhone, long goodsId, boolean useProcedure) { 55 | // 用户校验 56 | User user = userDao.queryByPhone(userPhone); 57 | if (user == null) { 58 | throw new BizException(ResultEnum.INVALID_USER.getMsg()); 59 | } 60 | if (useProcedure) { 61 | // 通过存储方式的方法进行操作 62 | Map map = new HashMap(); 63 | map.put("userId", user.getUserId()); 64 | map.put("goodsId", goodsId); 65 | map.put("title", "抢购"); 66 | map.put("result", null); 67 | goodsDao.bugWithProcedure(map); 68 | int result = MapUtils.getInteger(map, "result", -1); 69 | if (result <= 0) { 70 | // 买卖失败 71 | throw new BizException(ResultEnum.INNER_ERROR.getMsg()); 72 | } else { 73 | // 买卖成功 74 | // 此时缓存中的数据不是最新的,需要对缓存进行清理(具体的缓存策略还是要根据具体需求制定) 75 | cache.deleteCacheWithPattern("getGoodsList*"); 76 | LOG.info("delete cache with key: getGoodsList*"); 77 | return; 78 | } 79 | } else { 80 | 81 | int inserCount = orderDao.insertOrder(user.getUserId(), goodsId, "普通买卖"); 82 | if (inserCount <= 0) { 83 | // 买卖失败 84 | throw new BizException(ResultEnum.DB_UPDATE_RESULT_ERROR.getMsg()); 85 | } else { 86 | // 减库存 87 | int updateCount = goodsDao.reduceNumber(goodsId); 88 | if (updateCount <= 0) { 89 | // 减库存失败 90 | throw new BizException(ResultEnum.DB_UPDATE_RESULT_ERROR.getMsg()); 91 | } else { 92 | // 买卖成功 93 | // 此时缓存中的数据不再是最新的,需要对缓存进行清理(具体的缓存策略还是要根据具体需求制定) 94 | cache.deleteCacheWithPattern("getGoodsList*"); 95 | LOG.info("delete cache with key: getGoodsList*"); 96 | return; 97 | } 98 | } 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.service.impl; 2 | 3 | import com.yingjun.ssm.cache.RedisCache; 4 | import com.yingjun.ssm.dao.UserDao; 5 | import com.yingjun.ssm.entity.User; 6 | import com.yingjun.ssm.service.UserService; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.List; 13 | 14 | @Service 15 | public class UserServiceImpl implements UserService { 16 | 17 | private final Logger LOG = LoggerFactory.getLogger(this.getClass()); 18 | @Autowired 19 | private UserDao userDao; 20 | @Autowired 21 | private RedisCache cache; 22 | 23 | 24 | @Override 25 | public List getUserList(int offset, int limit) { 26 | String cache_key=RedisCache.CAHCENAME+"|getUserList|"+offset+"|"+limit; 27 | //先去缓存中取 28 | List result_cache=cache.getListCache(cache_key, User.class); 29 | if(result_cache==null){ 30 | //缓存中没有再去数据库取,并插入缓存(缓存时间为60秒) 31 | result_cache=userDao.queryAll(offset, limit); 32 | cache.putListCacheWithExpireTime(cache_key, result_cache, RedisCache.CAHCETIME); 33 | LOG.info("put cache with key:"+cache_key); 34 | }else{ 35 | LOG.info("get cache with key:"+cache_key); 36 | } 37 | return result_cache; 38 | } 39 | 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/util/CustomDateSerializer.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.util; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.JsonSerializer; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | 8 | import java.io.IOException; 9 | import java.text.SimpleDateFormat; 10 | import java.util.Date; 11 | 12 | /** 13 | * 自定义返回JSON 数据格中日期格式化处理 14 | * 15 | * @author yingjun 16 | * 17 | */ 18 | public class CustomDateSerializer extends JsonSerializer { 19 | 20 | @Override 21 | public void serialize(Date value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException, JsonProcessingException { 22 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 23 | jsonGenerator.writeString(sdf.format(value)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/util/ProtoStuffSerializerUtil.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.util; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.util.List; 7 | 8 | import com.dyuproject.protostuff.LinkedBuffer; 9 | import com.dyuproject.protostuff.ProtostuffIOUtil; 10 | import com.dyuproject.protostuff.Schema; 11 | import com.dyuproject.protostuff.runtime.RuntimeSchema; 12 | 13 | /** 14 | * 序列话工具 15 | */ 16 | public class ProtoStuffSerializerUtil { 17 | 18 | /** 19 | * 序列化对象 20 | * @param obj 21 | * @return 22 | */ 23 | public static byte[] serialize(T obj) { 24 | if (obj == null) { 25 | throw new RuntimeException("序列化对象(" + obj + ")!"); 26 | } 27 | @SuppressWarnings("unchecked") 28 | Schema schema = (Schema) RuntimeSchema.getSchema(obj.getClass()); 29 | LinkedBuffer buffer = LinkedBuffer.allocate(1024 * 1024); 30 | byte[] protostuff = null; 31 | try { 32 | protostuff = ProtostuffIOUtil.toByteArray(obj, schema, buffer); 33 | } catch (Exception e) { 34 | throw new RuntimeException("序列化(" + obj.getClass() + ")对象(" + obj + ")发生异常!", e); 35 | } finally { 36 | buffer.clear(); 37 | } 38 | return protostuff; 39 | } 40 | 41 | /** 42 | * 反序列化对象 43 | * @param paramArrayOfByte 44 | * @param targetClass 45 | * @return 46 | */ 47 | public static T deserialize(byte[] paramArrayOfByte, Class targetClass) { 48 | if (paramArrayOfByte == null || paramArrayOfByte.length == 0) { 49 | throw new RuntimeException("反序列化对象发生异常,byte序列为空!"); 50 | } 51 | T instance = null; 52 | try { 53 | instance = targetClass.newInstance(); 54 | } catch (InstantiationException | IllegalAccessException e) { 55 | throw new RuntimeException("反序列化过程中依据类型创建对象失败!", e); 56 | } 57 | Schema schema = RuntimeSchema.getSchema(targetClass); 58 | ProtostuffIOUtil.mergeFrom(paramArrayOfByte, instance, schema); 59 | return instance; 60 | } 61 | 62 | /** 63 | * 序列化列表 64 | * @param objList 65 | * @return 66 | */ 67 | public static byte[] serializeList(List objList) { 68 | if (objList == null || objList.isEmpty()) { 69 | throw new RuntimeException("序列化对象列表(" + objList + ")参数异常!"); 70 | } 71 | @SuppressWarnings("unchecked") 72 | Schema schema = (Schema) RuntimeSchema.getSchema(objList.get(0).getClass()); 73 | LinkedBuffer buffer = LinkedBuffer.allocate(1024 * 1024); 74 | byte[] protostuff = null; 75 | ByteArrayOutputStream bos = null; 76 | try { 77 | bos = new ByteArrayOutputStream(); 78 | ProtostuffIOUtil.writeListTo(bos, objList, schema, buffer); 79 | protostuff = bos.toByteArray(); 80 | } catch (Exception e) { 81 | throw new RuntimeException("序列化对象列表(" + objList + ")发生异常!", e); 82 | } finally { 83 | buffer.clear(); 84 | try { 85 | if (bos != null) { 86 | bos.close(); 87 | } 88 | } catch (IOException e) { 89 | e.printStackTrace(); 90 | } 91 | } 92 | 93 | return protostuff; 94 | } 95 | 96 | /** 97 | * 反序列化列表 98 | * @param paramArrayOfByte 99 | * @param targetClass 100 | * @return 101 | */ 102 | public static List deserializeList(byte[] paramArrayOfByte, Class targetClass) { 103 | if (paramArrayOfByte == null || paramArrayOfByte.length == 0) { 104 | throw new RuntimeException("反序列化对象发生异常,byte序列为空!"); 105 | } 106 | 107 | Schema schema = RuntimeSchema.getSchema(targetClass); 108 | List result = null; 109 | try { 110 | result = ProtostuffIOUtil.parseListFrom(new ByteArrayInputStream(paramArrayOfByte), schema); 111 | } catch (IOException e) { 112 | throw new RuntimeException("反序列化对象列表发生异常!", e); 113 | } 114 | return result; 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/util/TimeUtils.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.util; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | /** 7 | * TimeUtils 8 | * 9 | */ 10 | public class TimeUtils { 11 | private final static long minute = 60 * 1000;// 1分钟 12 | private final static long hour = 60 * minute;// 1小时 13 | private final static long day = 24 * hour;// 1天 14 | private final static long month = 31 * day;// 月 15 | private final static long year = 12 * month;// 年 16 | 17 | 18 | public static final SimpleDateFormat DATE_FORMAT_DATE_D = new SimpleDateFormat("yyyy-MM-dd"); 19 | public static final SimpleDateFormat DATE_FORMAT_DATE_M = new SimpleDateFormat("yyyy-MM-dd HH:mm"); 20 | public static final SimpleDateFormat DATE_FORMAT_DATE_S = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 21 | 22 | private TimeUtils() { 23 | throw new AssertionError(); 24 | } 25 | 26 | /** 27 | * long time to string 28 | * 29 | * @param timeInMillis 30 | * @param dateFormat 31 | * @return 32 | */ 33 | public static String getTime(long timeInMillis, SimpleDateFormat dateFormat) { 34 | return dateFormat.format(new Date(timeInMillis)); 35 | } 36 | 37 | /** 38 | * long time to string, format is {@link #DEFAULT_DATE_FORMAT} 39 | * 40 | * @param timeInMillis 41 | * @return 42 | */ 43 | public static String getTime(long timeInMillis) { 44 | return getTime(timeInMillis, DATE_FORMAT_DATE_S); 45 | } 46 | 47 | /** 48 | * get current time in milliseconds 49 | * 50 | * @return 51 | */ 52 | public static long getCurrentTimeInLong() { 53 | return System.currentTimeMillis(); 54 | } 55 | 56 | /** 57 | * get current time in milliseconds, format is {@link #DEFAULT_DATE_FORMAT} 58 | * 59 | * @return 60 | */ 61 | public static String getCurrentTimeInString() { 62 | return getTime(getCurrentTimeInLong()); 63 | } 64 | 65 | /** 66 | * get current time in milliseconds 67 | * 68 | * @return 69 | */ 70 | public static String getCurrentTimeInString(SimpleDateFormat dateFormat) { 71 | return getTime(getCurrentTimeInLong(), dateFormat); 72 | } 73 | 74 | 75 | public static String getTimeFormatText(Date date) { 76 | if (date == null) { 77 | return null; 78 | } 79 | long diff = new Date().getTime() - date.getTime(); 80 | long r = 0; 81 | if (diff > year) { 82 | r = (diff / year); 83 | return r + "年前"; 84 | } 85 | if (diff > month) { 86 | r = (diff / month); 87 | return r + "个月前"; 88 | } 89 | if (diff > day) { 90 | r = (diff / day); 91 | return r + "天前"; 92 | } 93 | if (diff > hour) { 94 | r = (diff / hour); 95 | return r + "小时前"; 96 | } 97 | if (diff > minute) { 98 | r = (diff / minute); 99 | return r + "分钟前"; 100 | } 101 | return "刚刚"; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/validator/CustomDateSerializer.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.validator; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.JsonSerializer; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | 8 | import java.io.IOException; 9 | import java.text.SimpleDateFormat; 10 | import java.util.Date; 11 | 12 | /** 13 | * 自定义返回JSON 数据格中日期格式化处理 14 | * 15 | * @author yingjun 16 | * 17 | */ 18 | public class CustomDateSerializer extends JsonSerializer { 19 | 20 | @Override 21 | public void serialize(Date value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException, JsonProcessingException { 22 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 23 | jsonGenerator.writeString(sdf.format(value)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/validator/Not999.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.validator; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * 自定义validator标签(和 hibernate validator组合使用) 11 | * 12 | * @author yingjun 13 | * 14 | */ 15 | @Constraint(validatedBy = Not999Validator.class) // 具体的实现 16 | @Target({ java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD }) 17 | @Retention(java.lang.annotation.RetentionPolicy.RUNTIME) 18 | @Documented 19 | public @interface Not999 { 20 | 21 | // 提示信息,可以写死,可以填写国际化的key 22 | String message() default "{com.yingjun.ssm.validator.not999}"; 23 | 24 | // 下面这两个属性必须添加 25 | Class[] groups() default {}; 26 | 27 | Class[] payload() default {}; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/validator/Not999Validator.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.validator; 2 | 3 | import javax.validation.ConstraintValidator; 4 | import javax.validation.ConstraintValidatorContext; 5 | 6 | public class Not999Validator implements ConstraintValidator { 7 | 8 | @Override 9 | public void initialize(Not999 arg0) { 10 | 11 | } 12 | 13 | @Override 14 | public boolean isValid(Long vaule, ConstraintValidatorContext context) { 15 | if(vaule==999){ 16 | return false; 17 | }else{ 18 | return true; 19 | } 20 | } 21 | 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/web/GoodsController.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.web; 2 | 3 | import com.yingjun.ssm.dto.BaseResult; 4 | import com.yingjun.ssm.entity.Goods; 5 | import com.yingjun.ssm.enums.ResultEnum; 6 | import com.yingjun.ssm.exception.BizException; 7 | import com.yingjun.ssm.service.GoodsService; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.validation.BindingResult; 14 | import org.springframework.web.bind.annotation.CookieValue; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.ResponseBody; 18 | 19 | import javax.validation.Valid; 20 | import java.util.List; 21 | 22 | @Controller 23 | @RequestMapping("/goods") 24 | public class GoodsController { 25 | 26 | private final Logger LOG = LoggerFactory.getLogger(this.getClass()); 27 | 28 | @Autowired 29 | private GoodsService goodsService; 30 | 31 | @RequestMapping(value = "/list", method = RequestMethod.GET) 32 | public String list(Model model, Integer offset, Integer limit) { 33 | LOG.info("invoke----------/goods/list"); 34 | offset = offset == null ? 0 : offset;//默认便宜0 35 | limit = limit == null ? 50 : limit;//默认展示50条 36 | List list = goodsService.getGoodsList(offset, limit); 37 | model.addAttribute("goodslist", list); 38 | return "goodslist"; 39 | } 40 | 41 | @RequestMapping(value = "/{goodsId}/buy", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"}) 42 | @ResponseBody 43 | public BaseResult buy(@CookieValue(value = "userPhone", required = false) Long userPhone, 44 | /*@PathVariable("goodsId") Long goodsId*/ @Valid Goods goods, BindingResult result) { 45 | LOG.info("invoke----------/" + goods.getGoodsId() + "/buy userPhone:" + userPhone); 46 | if (userPhone == null) { 47 | return new BaseResult(false, ResultEnum.INVALID_USER.getMsg()); 48 | } 49 | //Valid 参数验证(这里注释掉,采用AOP的方式验证,见BindingResultAop.java) 50 | //if (result.hasErrors()) { 51 | // String errorInfo = "[" + result.getFieldError().getField() + "]" + result.getFieldError().getDefaultMessage(); 52 | // return new BaseResult(false, errorInfo); 53 | //} 54 | try { 55 | goodsService.buyGoods(userPhone, goods.getGoodsId(), false); 56 | } catch (BizException e) { 57 | return new BaseResult(false, e.getMessage()); 58 | } catch (Exception e) { 59 | return new BaseResult(false, ResultEnum.INNER_ERROR.getMsg()); 60 | } 61 | return new BaseResult(true, null); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/yingjun/ssm/web/UserController.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.web; 2 | 3 | import com.yingjun.ssm.entity.User; 4 | import com.yingjun.ssm.service.UserService; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | 13 | import java.util.List; 14 | 15 | @Controller 16 | @RequestMapping("/user") 17 | public class UserController { 18 | 19 | private final Logger LOG = LoggerFactory.getLogger(this.getClass()); 20 | 21 | @Autowired 22 | private UserService userService; 23 | 24 | @RequestMapping(value = "/list", method = RequestMethod.GET) 25 | public String list(Model model, Integer offset, Integer limit) { 26 | LOG.info("invoke----------/user/list"); 27 | offset = offset == null ? 0 : offset;//默认便宜0 28 | limit = limit == null ? 50 : limit;//默认展示50条 29 | List list = userService.getUserList(offset, limit); 30 | model.addAttribute("userlist", list); 31 | return "userlist"; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/ValidationMessages.properties: -------------------------------------------------------------------------------- 1 | javax.validation.constraints.AssertFalse.message = \u53EA\u80FD\u4E3Afalse 2 | javax.validation.constraints.AssertTrue.message = \u53EA\u80FD\u4E3Atrue 3 | javax.validation.constraints.DecimalMax.message = \u5FC5\u987B\u5C0F\u4E8E\u6216\u7B49\u4E8E{value} 4 | javax.validation.constraints.DecimalMin.message = \u5FC5\u987B\u5927\u4E8E\u6216\u7B49\u4E8E{value} 5 | javax.validation.constraints.Digits.message = \u6570\u5B57\u7684\u503C\u8D85\u51FA\u4E86\u5141\u8BB8\u8303\u56F4(\u53EA\u5141\u8BB8\u5728{integer}\u4F4D\u6574\u6570\u548C{fraction}\u4F4D\u5C0F\u6570\u8303\u56F4\u5185) 6 | javax.validation.constraints.Future.message = \u9700\u8981\u662F\u4E00\u4E2A\u5C06\u6765\u7684\u65F6\u95F4 7 | javax.validation.constraints.Max.message = \u6700\u5927\u4E0D\u80FD\u8D85\u8FC7{value} 8 | javax.validation.constraints.Min.message = \u6700\u5C0F\u4E0D\u80FD\u5C0F\u4E8E{value} 9 | javax.validation.constraints.NotNull.message = \u4E0D\u80FD\u4E3Anull 10 | javax.validation.constraints.Null.message = \u5FC5\u987B\u4E3Anull 11 | javax.validation.constraints.Past.message = \u9700\u8981\u662F\u4E00\u4E2A\u8FC7\u53BB\u7684\u4E8B\u4EF6 12 | javax.validation.constraints.Pattern.message = \u9700\u8981\u5339\u914D\u6B63\u5219\u8868\u8FBE\u5F0F"{regexp}" 13 | javax.validation.constraints.Size.message = \u4E2A\u6570\u5FC5\u987B\u5728{min}\u548C{max}\u4E4B\u95F4 14 | 15 | org.hibernate.validator.constraints.CreditCardNumber.message = \u4E0D\u5408\u6CD5\u7684\u4FE1\u7528\u5361\u53F7\u7801 16 | org.hibernate.validator.constraints.Email.message = \u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740 17 | org.hibernate.validator.constraints.Length.message = \u957F\u5EA6\u9700\u8981\u5728{min}\u548C{max}\u4E4B\u95F4 18 | org.hibernate.validator.constraints.NotBlank.message = \u4E0D\u80FD\u4E3A\u7A7A 19 | org.hibernate.validator.constraints.NotEmpty.message = \u4E0D\u80FD\u4E3A\u7A7A 20 | org.hibernate.validator.constraints.Range.message = \u9700\u8981\u5728{min}\u548C{max}\u4E4B\u95F4 21 | org.hibernate.validator.constraints.SafeHtml.message = \u53EF\u80FD\u6709\u4E0D\u5B89\u5168\u7684HTML\u5185\u5BB9 22 | org.hibernate.validator.constraints.ScriptAssert.message = \u6267\u884C\u811A\u672C\u8868\u8FBE\u5F0F"{script}"\u6CA1\u6709\u80FD\u591F\u5F97\u5230true 23 | org.hibernate.validator.constraints.URL.message = \u9700\u8981\u662F\u4E00\u4E2A\u5408\u6CD5\u7684URL 24 | 25 | com.yingjun.ssm.validator.not999 =\u4E0D\u80FD\u7B49\u4E8E999 -------------------------------------------------------------------------------- /src/main/resources/jdbc.properties: -------------------------------------------------------------------------------- 1 | jdbc.driverClassName=com.mysql.jdbc.Driver 2 | jdbc.url=jdbc:mysql://127.0.0.1:3306/beauty_ssm?useUnicode=true&characterEncoding=UTF-8 3 | jdbc.username=root 4 | jdbc.password=yingjun 5 | 6 | druid.pool.size.max=20 7 | druid.pool.size.min=3 8 | druid.pool.size.init=3 9 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=info, console, log, error 2 | 3 | ###Console ### 4 | log4j.appender.console = org.apache.log4j.ConsoleAppender 5 | log4j.appender.console.Target = System.out 6 | log4j.appender.console.layout = org.apache.log4j.PatternLayout 7 | log4j.appender.console.layout.ConversionPattern = %d %p[%C:%L]- %m%n 8 | 9 | ### log ### 10 | log4j.appender.log = org.apache.log4j.DailyRollingFileAppender 11 | log4j.appender.log.File = ${catalina.base}/logs/debug.log 12 | log4j.appender.log.Append = true 13 | log4j.appender.log.Threshold = DEBUG 14 | log4j.appender.log.DatePattern='.'yyyy-MM-dd 15 | log4j.appender.log.layout = org.apache.log4j.PatternLayout 16 | log4j.appender.log.layout.ConversionPattern = %d %p[%c:%L] - %m%n 17 | 18 | 19 | ### Error ### 20 | log4j.appender.error = org.apache.log4j.DailyRollingFileAppender 21 | log4j.appender.error.File = ${catalina.base}/logs/error.log 22 | log4j.appender.error.Append = true 23 | log4j.appender.error.Threshold = ERROR 24 | log4j.appender.error.DatePattern='.'yyyy-MM-dd 25 | log4j.appender.error.layout = org.apache.log4j.PatternLayout 26 | log4j.appender.error.layout.ConversionPattern =%d %p[%c:%L] - %m%n 27 | 28 | ###\u8F93\u51FASQL 29 | log4j.logger.com.ibatis=DEBUG 30 | log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG 31 | log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG 32 | log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG 33 | log4j.logger.java.sql.Connection=DEBUG 34 | log4j.logger.java.sql.Statement=DEBUG 35 | log4j.logger.java.sql.PreparedStatement=DEBUG -------------------------------------------------------------------------------- /src/main/resources/mapper/GoodsDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | UPDATE _goods 15 | SET 16 | number = number -1 17 | WHERE 18 | goods_id = #{goodsId} 19 | AND state = 1 20 | AND number >0; 21 | 22 | 23 | 24 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/resources/mapper/OrderDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | INSERT INTO 8 | _order(user_id,goods_id,title) 9 | VALUES 10 | (#{userId},#{goodsId},#{title}) 11 | 12 | 13 | 22 | 23 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 18 | 19 | 20 | 21 | UPDATE _user 22 | SET 23 | score = score + #{add} 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/mybatis-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/resources/redis.properties: -------------------------------------------------------------------------------- 1 | #redis config 2 | redis.pass=yingjun 3 | redis.pool.maxTotal=105 4 | redis.pool.maxIdle=10 5 | redis.pool.maxWaitMillis=5000 6 | redis.pool.testOnBorrow=true 7 | 8 | #redis \u5355\u8282\u70B9\u914D\u7F6E 9 | redis.ip=192.168.xx.xxx 10 | redis.port=6379 11 | 12 | #redis \u9AD8\u53EF\u7528\u914D\u7F6E(\u57FA\u4E8Eredis sentinel) 13 | #sentinel1.ip=192.168.11.100 14 | #sentinel1.port=63791 15 | #sentinel2.ip=192.168.11.101 16 | #sentinel2.port=63792 17 | #sentinel3.ip=192.168.11.102 18 | #sentinel3.port=63792 19 | 20 | #redis \u96C6\u7FA4\u9AD8\u53EF\u7528\u914D\u7F6E\uFF08\u57FA\u4E8Ereids cluster\uFF09 21 | #redis.ip1=192.168.11.100 22 | #redis.port1=7111 23 | #redis.ip2=192.168.11.101 24 | #redis.port2=7112 25 | #redis.ip3=192.168.11.102 26 | #redis.port3=7113 27 | #redis.ip4=192.168.11.103 28 | #redis.port4=7114 29 | #redis.ip5=192.168.11.104 30 | #redis.port5=7115 31 | #redis.ip6=192.168.11.105 32 | #redis.port6=7116 -------------------------------------------------------------------------------- /src/main/resources/spring/spring-dao.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/resources/spring/spring-quartz.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/spring/spring-redis.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 57 | 58 | 59 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/main/resources/spring/spring-service.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/resources/spring/spring-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/sql/execute_bug.sql: -------------------------------------------------------------------------------- 1 | -- 买逻辑的存储过程 2 | DELIMITER $$ -- 结束标志;转换为 $$ 3 | -- 定义存储过程 4 | -- 参数:in 输入参数; out 输出参数 5 | -- row_count():返回上一条修改类型sql(delete,insert,upodate)的影响行数 6 | -- row_count: 0:未修改数据; >0:表示修改的行数; <0:sql错误/未执行修改sql 7 | CREATE PROCEDURE `beauty_ssm`.`execute_buy` 8 | (IN v_user_id BIGINT, 9 | IN v_goods_id BIGINT, 10 | IN v_title VARCHAR(120), 11 | OUT r_result INT) 12 | BEGIN 13 | DECLARE insert_count INT DEFAULT 0; 14 | START TRANSACTION; 15 | INSERT INTO _order (user_id, goods_id, title) 16 | VALUES(v_user_id, v_goods_id, v_title); 17 | SELECT ROW_COUNT() INTO insert_count; 18 | IF (insert_count = 0) THEN 19 | ROLLBACK; 20 | SET r_result = -1; 21 | ELSEIF (insert_count < 0) THEN 22 | ROLLBACK ; 23 | SET r_result = -2; 24 | ELSE 25 | UPDATE _goods SET number = number - 1 26 | WHERE goods_id = v_goods_id 27 | AND number > 0; 28 | SELECT ROW_COUNT() INTO insert_count; 29 | IF (insert_count = 0) THEN 30 | ROLLBACK; 31 | SET r_result = 0; 32 | ELSEIF (insert_count < 0) THEN 33 | ROLLBACK; 34 | SET r_result = -2; 35 | ELSE 36 | COMMIT; 37 | SET r_result = 1; 38 | END IF; 39 | END IF; 40 | END; 41 | $$ 42 | -- 代表存储过程定义结束 43 | 44 | 45 | DELIMITER ; -- 结束标志换回默认的; 46 | SET @r_result = -3; 47 | -- 执行存储过程 48 | call execute_buy(1000,1000,'抢购iphone', @r_result); 49 | -- 获取结果 50 | SELECT @r_result; 51 | 52 | -- 存储过程 53 | -- 1.存储过程优化:事务行级锁持有的时间 54 | -- 2.不要过度依赖存储过程 55 | -- 3.简单的逻辑可以应用存储过程 56 | -- 4.QPS:一个秒杀单6000/qps 57 | 58 | -------------------------------------------------------------------------------- /src/main/resources/sql/schema.sql: -------------------------------------------------------------------------------- 1 | 2 | -- 需要 MySQL 5.6.5以上的版本 3 | CREATE DATABASE beauty_ssm; 4 | USE beauty_ssm; 5 | 6 | -- 用户表 7 | CREATE TABLE _user( 8 | `user_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID', 9 | `user_name` VARCHAR(50) NOT NULL COMMENT '用户名', 10 | `user_phone` BIGINT NOT NULL COMMENT '手机号', 11 | `score` INT NOT NULL COMMENT '积分', 12 | `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 13 | PRIMARY KEY (`user_id`), 14 | KEY `idx_user_phone`(`user_phone`) 15 | )ENGINE=INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='用户表'; 16 | 17 | -- 商品表 18 | CREATE TABLE _goods( 19 | `goods_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '商品ID', 20 | `title` VARCHAR(120) NOT NULL COMMENT '商品名称', 21 | `state` INT NOT NULL COMMENT '商品状态', 22 | `price` FLOAT NOT NULL COMMENT '商品价格', 23 | `number` INT NOT NULL COMMENT '商品数量', 24 | `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 25 | `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', 26 | PRIMARY KEY (`goods_id`) 27 | )ENGINE=INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='商品表'; 28 | 29 | -- 订单表 30 | CREATE TABLE _order( 31 | `order_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单ID', 32 | `user_id` BIGINT NOT NULL COMMENT '用户ID', 33 | `goods_id` BIGINT NOT NULL COMMENT '商品ID', 34 | `title` VARCHAR(120) NOT NULL COMMENT '订单名称', 35 | `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 36 | PRIMARY KEY (`order_id`), 37 | KEY `idx_user_id`(`user_id`), 38 | KEY `idx_goods_id`(`goods_id`) 39 | )ENGINE=INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='订单表'; 40 | 41 | 42 | -- 插入初始数据 43 | INSERT INTO 44 | _user(user_name, user_phone, score) 45 | VALUES 46 | ('阿坚', 18768128888, 0), 47 | ('小明', 18968129999, 0); 48 | 49 | 50 | INSERT INTO 51 | _goods(title, state, price,number) 52 | VALUES 53 | ('iphone7', 1, 3999, 100), 54 | ('ipad3', 1, 1999, 2000); 55 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/common/head.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/common/tag.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 3 | <% 4 | request.setCharacterEncoding("utf-8"); 5 | String path = request.getContextPath(); 6 | %> -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/goodslist.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=UTF-8" language="java"%> 2 | <%@include file="common/tag.jsp"%> 3 | 4 | 5 | 6 | 商品列表 7 | <%@include file="common/head.jsp"%> 8 | 9 | 10 |
11 |
12 |
13 |

商品列表

14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
商品ID商品名称商品价格商品状态商品数量操作
${goods.goodsId}${goods.title}${goods.price}已下架 销售中${goods.number}
40 | 41 |
42 |
43 |
44 | 45 | 46 | <%--登录弹出层 输入电话--%> 47 | 75 | 76 | 77 | 78 | 79 | 85 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/userlist.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=UTF-8" language="java"%> 2 | <%@include file="common/tag.jsp"%> 3 | 4 | 5 | 6 | 用户列表 7 | <%@include file="common/head.jsp"%> 8 | 9 | 10 |
11 |
12 |
13 |

用户列表

14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
用户ID用户名手机号积分
${user.userId}${user.userName}${user.userPhone}${user.score}
37 | 38 |
39 |
40 |
41 | 42 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | spring-dispatcher 10 | org.springframework.web.servlet.DispatcherServlet 11 | 12 | 13 | contextConfigLocation 14 | classpath:spring/spring-*.xml 15 | 16 | 1 17 | 18 | 19 | spring-dispatcher 20 | 21 | / 22 | 23 | 24 | 25 | 26 | DruidStatView 27 | com.alibaba.druid.support.http.StatViewServlet 28 | 29 | 30 | DruidStatView 31 | /druid/* 32 | 33 | 34 | 35 | DruidWebStatFilter 36 | com.alibaba.druid.support.http.WebStatFilter 37 | 38 | exclusions 39 | *.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/* 40 | 41 | 42 | 43 | DruidWebStatFilter 44 | /* 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/webapp/index.jsp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wosyingjun/beauty_ssm/47900a0a6c573e7080fc43b0750a8eb7b38a0526/src/main/webapp/index.jsp -------------------------------------------------------------------------------- /src/main/webapp/resource/script/handler.js: -------------------------------------------------------------------------------- 1 | //存放主要交互逻辑的js代码 2 | 3 | var handler = { 4 | //封装相关ajax的url 5 | URL: { 6 | goodsBuy: function (goodsId) { 7 | return '/beauty_ssm/goods/' + goodsId + '/buy'; 8 | } 9 | }, 10 | //验证手机号 11 | validatePhone: function (phone) { 12 | if (phone && phone.length == 11 && !isNaN(phone)) { 13 | return true;//直接判断对象会看对象是否为空,空就是undefine就是false; isNaN 非数字返回true 14 | } else { 15 | return false; 16 | } 17 | }, 18 | //详情页秒杀逻辑 19 | goods: { 20 | //详情页初始化 21 | init: function (params) { 22 | //在cookie中查找手机号 23 | var userPhone = $.cookie('userPhone'); 24 | //验证手机号 25 | if (!handler.validatePhone(userPhone)) { 26 | //绑定手机 控制输出 27 | var loginModal = $('#loginModal'); 28 | loginModal.modal({ 29 | show: true,//显示弹出层 30 | backdrop: 'static',//禁止位置关闭 31 | keyboard: false//关闭键盘事件 32 | }); 33 | 34 | $('#loginBtn').click(function () { 35 | var inputPhone = $('#userPhone').val(); 36 | if (handler.validatePhone(inputPhone)) { 37 | //电话写入cookie(7天过期) 38 | $.cookie('userPhone', inputPhone, {expires: 7}); 39 | //验证通过  刷新页面 40 | window.location.reload(); 41 | } else { 42 | $('#userPhoneMessage').hide().html('').show(300); 43 | } 44 | }); 45 | } 46 | } 47 | }, 48 | 49 | goodsBuy: function (goodsId) { 50 | //执行购买请求 51 | $.post(handler.URL.goodsBuy(goodsId), {}, function (result) { 52 | if (result && result['success']) { 53 | alert("购买成功!"); 54 | window.location.reload(); 55 | }else{ 56 | alert(result['error']); 57 | } 58 | 59 | }); 60 | 61 | }, 62 | 63 | 64 | } -------------------------------------------------------------------------------- /src/test/java/com/yingjun/ssm/dao/GoodsDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.dao; 2 | 3 | import com.yingjun.ssm.entity.Goods; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @ContextConfiguration("classpath:spring/spring-dao.xml") 16 | public class GoodsDaoTest { 17 | 18 | @Autowired 19 | private GoodsDao goodsDao; 20 | 21 | @Test 22 | public void testQueryAll() { 23 | List list=goodsDao.queryAll(0, 100); 24 | for (Goods goods : list) { 25 | System.out.println(goods); 26 | } 27 | System.out.println("--------------------------"); 28 | } 29 | 30 | @Test 31 | public void testReduceNumber() { 32 | int result=goodsDao.reduceNumber(1000); 33 | System.out.println("testReduceNumber result:"+result); 34 | System.out.println("--------------------------"); 35 | } 36 | 37 | @Test 38 | public void testBugWithProcedure() { 39 | Map map=new HashMap(); 40 | map.put("userId", 1000L); 41 | map.put("goodsId", 1000L); 42 | map.put("title", "抢购iPhone7"); 43 | map.put("result", null); 44 | goodsDao.bugWithProcedure(map); 45 | //获取result 46 | System.out.println("testBugWithProcedure result:"+map.get("result")); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/yingjun/ssm/dao/OrderDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.dao; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | 13 | import com.yingjun.ssm.entity.Goods; 14 | import com.yingjun.ssm.entity.Order; 15 | 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @ContextConfiguration("classpath:spring/spring-dao.xml") 18 | public class OrderDaoTest { 19 | 20 | @Autowired 21 | private OrderDao orderDao; 22 | @Autowired 23 | private GoodsDao goodsDao; 24 | 25 | @Test 26 | public void testInsertOrder() { 27 | Goods goods=goodsDao.queryAll(0, 1).get(0); 28 | System.out.println(goods); 29 | int result=orderDao.insertOrder(1000,goods.getGoodsId(),goods.getTitle()); 30 | System.out.println("testInsertOrder result:"+result); 31 | System.out.println("--------------------------"); 32 | } 33 | 34 | @Test 35 | public void testQueryByUserPhone() { 36 | List list=orderDao.queryByUserPhone(18768128888L); 37 | for (Order order : list) { 38 | System.out.println(order); 39 | } 40 | System.out.println("--------------------------"); 41 | } 42 | 43 | @Test 44 | public void testQueryAll() { 45 | List list=new ArrayList(); 46 | for (Order order : list) { 47 | System.out.println(order); 48 | } 49 | System.out.println("--------------------------"); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/yingjun/ssm/dao/UserDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.yingjun.ssm.dao; 2 | 3 | 4 | import java.util.List; 5 | 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | 13 | import com.yingjun.ssm.entity.User; 14 | 15 | /** 16 | * 17 | * @author yingjun 18 | * 19 | */ 20 | @RunWith(SpringJUnit4ClassRunner.class) 21 | @ContextConfiguration("classpath:spring/spring-dao.xml") 22 | public class UserDaoTest { 23 | 24 | @Autowired 25 | private UserDao userDao; 26 | 27 | @Test 28 | public void testQueryById() { 29 | User user=userDao.queryByPhone(18768128888L); 30 | System.out.println(user); 31 | System.out.println("--------------------------"); 32 | } 33 | 34 | @Test 35 | public void testQueryAll() { 36 | List list=userDao.queryAll(0, 100); 37 | for (User user : list) { 38 | System.out.println(user); 39 | } 40 | } 41 | 42 | @Test 43 | public void testAddScore() { 44 | userDao.addScore(10); 45 | List list=userDao.queryAll(0, 100); 46 | for (User user : list) { 47 | System.out.println(user); 48 | } 49 | } 50 | 51 | } 52 | --------------------------------------------------------------------------------