什么是礼品网
采取礼品代发的方法,可以让店铺用比较低的成本,拥有真实的发货物流,而且不需要自己去处理发货。那么靠谱的礼品代发平台有什么特点?选择礼品代发平台应该看什么?
礼品代发的意义。
当你有一个礼品需要寄给买家,你起码需要以下几个步骤:
虽说整个流程没有技术难度,大家每天也在做这个事情,但如果每天需要发10个,甚至100个礼品单,就会消耗大量的人工和时间成本,算上礼品和快递费的成本,长此以往也是笔不小的开销,为了控制成本留下更多的利润,需要以更低的成本,更高的效率,更便捷的方式去发礼品单。
如何搭建礼品网?怎么搭建一个礼品网?
礼品网分别,用户端,站点可以进行商品的上架,下架管理等操作,支持一键对接第三方云仓库,一键发货
开发工具:
前端: vue
服务端:springboot + mysql + mybaits
源码展示
验证码获取
package cn.gift.web.service.system;import cn.gift.web.constants.BusinessException;import cn.gift.web.constants.RedisKeyConstants;import cn.plus.core.exception.ServiceException;import cn.plus.core.utils.SerialNumber;import com.google.code.kaptcha.Producer;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import mons.lang3.StringUtils;import org.apache.tomcat.util.codec.binary.Base64;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Service;import javax.imageio.ImageIO;import java.awt.image.BufferedImage;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.concurrent.TimeUnit;/*** @Description: 校准需要的内容* @author: RaveyXie* @date: 11月13日 7:59 PM* @since*/@Service@Slf4j@RequiredArgsConstructorpublic class CheckVerifyCodeService {private final StringRedisTemplate masterRedisTemplate;private final Producer producer;/*** 校准验证码** @param key* @param code* @param isRemoveKey 判断是否删除验证码 用来做发送短信就要校验验证码*/public void checkVerifyCode(String key, String code, Boolean isRemoveKey) {// 校准验证码if (StringUtils.isEmpty(code)) {throw new ServiceException(BusinessException.CODE_CANNOT_EMPTY);}String redisCode = RedisKeyConstants.KAPTCHA.setArg(key);String oldCode = masterRedisTemplate.opsForValue().get(redisCode);if (isRemoveKey) {masterRedisTemplate.delete(redisCode);}if (StringUtils.isEmpty(oldCode)) {throw new ServiceException(BusinessException.VERIFICATION_CODE_HAS_EXPIRED);}if (pareToIgnoreCase(code) != 0) {throw new ServiceException(BusinessException.VERIFICATION_CODE_INPUT_ERROR);}}/*** 生成验证码** @return*/public Object doKaptcha() {// 生成验证码String text = producer.createText();BufferedImage image = producer.createImage(text);// 将验证码的信息存在session中进行比对Long serialNum7 = SerialNumber.key7();String key = RedisKeyConstants.KAPTCHA.setArg(serialNum7.toString());// 存在redis里面 设置10分钟过期masterRedisTemplate.opsForValue().set(key, text, 3, TimeUnit.MINUTES);// 这一步就要存sessionMap<String, Object> maps = new HashMap<>(16);maps.put("key", serialNum7);try {ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ImageIO.write(image, "jpeg", outputStream);String base64 = Base64.encodeBase64String(outputStream.toByteArray());String captchaBase64 = "data:image/jpeg;base64," + base64.replaceAll("\r\n", "");maps.put("value", captchaBase64);return maps;} catch (IOException e) {return null;}}}
敏感词校验
package cn.gift.web.service.business;import cn.gift.web.dao.SensitiveWordsInfoDao;import cn.gift.web.model.SensitiveWordsInfo;import cn.gift.web.service.tenant.TenantService;import cn.plus.core.jdbc.PageResult;import com.github.pagehelper.PageInfo;import com.github.pagehelper.PageRowBounds;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;/*** @Description: 敏感词信息* @author: RaveyXie* @date: 11月26日 2:42 PM* @since*/@Service@RequiredArgsConstructor@Slf4jpublic class SensitiveWordsService {private final SensitiveWordsInfoDao sensitiveWordsInfoDao;private final TenantService tenantService;/*** 获取敏感词信息** @param sensitiveStr* @param pageRowBounds* @return*/public PageResult<SensitiveWordsInfo> getSensitiveWords(String sensitiveStr, PageRowBounds pageRowBounds) {PageInfo<SensitiveWordsInfo> pageInfo = sensitiveWordsInfoDao.getSensitiveWords(sensitiveStr, pageRowBounds);return new PageResult<>(pageInfo.getList(), pageInfo.getTotal(), pageRowBounds.getOffset() / pageRowBounds.getLimit() + 1, pageRowBounds.getLimit());}public void savaOrUpdateSensitiveWords(SensitiveWordsInfo sensitiveWordsInfo) {tenantService.checkIsAdmin();sensitiveWordsInfoDao.saveOrUpdate(sensitiveWordsInfo);}public void removeSensitiveWords(Long id) {tenantService.checkIsAdmin();sensitiveWordsInfoDao.removeById(id);}}
关于密码加密
package cn.gift.web.util;import mon.collect.Lists;import mons.logging.Log;import mons.logging.LogFactory;import org.apache.tomcat.util.codec.binary.Base64;import javax.crypto.Cipher;import java.nio.charset.StandardCharsets;import java.security.*;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import java.util.*;/*** 进行rsa加密 像 RSA 这样的非对称密码旨在加密短数据,通常是对称 key ,而大数据使用对称分组密码加密(对称 key 将与非对称密码交换)。* StackOverflow 上确实有很多类似的问题和答案。 This one是一个提供了很好的答案。* 即使这样确实可以解决大数据量加密的问题,但是前端输入的长度是不可控制的 所以我们这边就进行一个操作* 对大数据量的接口进行过滤。对文件类型接口进行过滤不进行加密* 1.前端加密规则,因为存在中文所以前端加密会导致后端乱码所以加密规则定下* - 先进行参数的base64编码* - 在进行rsa加密* - 在进行base64编码* 我们进行长数据的接口进行过滤** @author raveyxie*/public class RSAUtils {protected static final Log log = LogFactory.getLog(RSAUtils.class);private static String KEY_RSA_TYPE = "RSA";private static String KEY_RSA_TYPE_ALL = "RSA/ECB/PKCS1Padding";private static int KEY_SIZE = 1024;private static int ENCODE_PART_SIZE = KEY_SIZE / 8;public static final String PUBLIC_KEY_NAME = "public";public static final String PRIVATE_KEY_NAME = "private";/*** 创建公钥秘钥** @return*/public static Map<String, String> createRSAKeys() {//里面存放公私秘钥的Base64位加密Map<String, String> keyPairMap = new HashMap<>();try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_RSA_TYPE);keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());KeyPair keyPair = keyPairGenerator.generateKeyPair();//获取公钥秘钥String publicKeyValue = Base64.encodeBase64String(keyPair.getPublic().getEncoded());String privateKeyValue = Base64.encodeBase64String(keyPair.getPrivate().getEncoded());//存入公钥秘钥,以便以后获取keyPairMap.put(PUBLIC_KEY_NAME, publicKeyValue);keyPairMap.put(PRIVATE_KEY_NAME, privateKeyValue);} catch (NoSuchAlgorithmException e) {log.error("当前JDK版本没找到RSA加密算法!");e.printStackTrace();}return keyPairMap;}/*** 公钥加密* 描述:* 1字节 = 8位;* 最大加密长度如 1024位私钥时,最大加密长度为 128-11 = 117字节,不管多长数据,加密出来都是 128 字节长度。** @param sourceStr* @param publicKeyBase64Str* @return*/public static String encode(String sourceStr, String publicKeyBase64Str) {byte[] publicBytes = Base64.decodeBase64(publicKeyBase64Str);//公钥加密X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicBytes);List<byte[]> alreadyEncodeListData = new LinkedList<>();int maxEncodeSize = ENCODE_PART_SIZE - 11;String encodeBase64Result = null;try {KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE_ALL);cipher.init(Cipher.ENCRYPT_MODE, publicKey);byte[] sourceBytes = sourceStr.getBytes(StandardCharsets.UTF_8);int sourceLen = sourceBytes.length;for (int i = 0; i < sourceLen; i += maxEncodeSize) {int curPosition = sourceLen - i;int tempLen = curPosition;if (curPosition > maxEncodeSize) {tempLen = maxEncodeSize;}//待加密分段数据byte[] tempBytes = new byte[tempLen];System.arraycopy(sourceBytes, i, tempBytes, 0, tempLen);byte[] tempAlreadyEncodeData = cipher.doFinal(tempBytes);alreadyEncodeListData.add(tempAlreadyEncodeData);}//加密次数int partLen = alreadyEncodeListData.size();int allEncodeLen = partLen * ENCODE_PART_SIZE;//存放所有RSA分段加密数据byte[] encodeData = new byte[allEncodeLen];for (int i = 0; i < partLen; i++) {byte[] tempByteList = alreadyEncodeListData.get(i);System.arraycopy(tempByteList, 0, encodeData, i * ENCODE_PART_SIZE, ENCODE_PART_SIZE);}encodeBase64Result = Base64.encodeBase64String(encodeData);} catch (Exception e) {e.printStackTrace();}return encodeBase64Result;}/*** 私钥解密* 1.前端加密规则,因为存在中文所以前端加密会导致后端乱码所以加密规则定下* - 先进行参数的base64编码* - 在进行rsa加密* - 在进行base64编码** @param sourceBase64RSA* @param privateKeyBase64Str*/public static String decode(String sourceBase64RSA, String privateKeyBase64Str) {byte[] privateBytes = Base64.decodeBase64(privateKeyBase64Str);byte[] encodeSource = Base64.decodeBase64(sourceBase64RSA);int encodePartLen = encodeSource.length / ENCODE_PART_SIZE;//所有解密数据List<byte[]> decodeListData = new LinkedList<>();String decodeStrResult = null;//私钥解密PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateBytes);try {KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE_ALL);cipher.init(Cipher.DECRYPT_MODE, privateKey);//初始化所有被解密数据长度int allDecodeByteLen = 0;for (int i = 0; i < encodePartLen; i++) {byte[] tempEncodedData = new byte[ENCODE_PART_SIZE];System.arraycopy(encodeSource, i * ENCODE_PART_SIZE, tempEncodedData, 0, ENCODE_PART_SIZE);byte[] decodePartData = cipher.doFinal(tempEncodedData);decodeListData.add(decodePartData);allDecodeByteLen += decodePartData.length;}byte[] decodeResultBytes = new byte[allDecodeByteLen];for (int i = 0, curPosition = 0; i < encodePartLen; i++) {byte[] tempSorceBytes = decodeListData.get(i);int tempSourceBytesLen = tempSorceBytes.length;System.arraycopy(tempSorceBytes, 0, decodeResultBytes, curPosition, tempSourceBytesLen);curPosition += tempSourceBytesLen;}decodeStrResult = new String(decodeResultBytes, StandardCharsets.UTF_8);} catch (Exception e) {e.printStackTrace();}return new String(Base64.decodeBase64(decodeStrResult));}/*** 过滤链接*/public static final List<String> NO_RSA_URI = Lists.newArrayList("/business/basic/cutAddress","/business/order/saveOrder","/business/basic/templateImport");/*** 获取过滤链接** @param webFirst* @return*/public static List<String> getFilterUrlInfo(String webFirst) {List<String> webFilter = new ArrayList<>();RSAUtils.NO_RSA_URI.forEach(item -> webFilter.add(webFirst + item));return webFilter;}}
加密算法
package cn.gift.web.util;/** Copyright 2002- the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import lombok.extern.slf4j.Slf4j;import java.security.SecureRandom;import java.util.regex.Pattern;/*** Implementation of PasswordEncoder that uses the BCrypt strong hashing function. Clients* can optionally supply a "strength" (a.k.a. log rounds in BCrypt) and a SecureRandom* instance. The larger the strength parameter the more work will have to be done* (exponentially) to hash the passwords. The default value is 10.* 进行密码的加密* @author Dave Syer*/@Slf4jpublic class BCryptPasswordEncoder {private final int strength;private final SecureRandom random;private Pattern BCRYPT_PATTERN = pile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}");public BCryptPasswordEncoder() {this(-1);}/*** @param strength the log rounds to use, between 4 and 31*/public BCryptPasswordEncoder(int strength) {this(strength, null);}/*** @param strength the log rounds to use, between 4 and 31* @param random the secure random instance to use*/public BCryptPasswordEncoder(int strength, SecureRandom random) {if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {throw new IllegalArgumentException("Bad strength");}this.strength = strength;this.random = random;}public String encode(CharSequence rawPassword) {String salt;if (strength > 0) {if (random != null) {salt = BCrypt.gensalt(strength, random);} else {salt = BCrypt.gensalt(strength);}} else {salt = BCrypt.gensalt();}return BCrypt.hashpw(rawPassword.toString(), salt);}public boolean matches(CharSequence rawPassword, String encodedPassword) {if (encodedPassword == null || encodedPassword.length() == 0) {log.warn("Empty encoded password");return false;}if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {log.warn("Encoded password does not look like BCrypt");return false;}return BCrypt.checkpw(rawPassword.toString(), encodedPassword);}}
前端代码
<template><div id="wxLogin"><div v-if="type == -1" class="form"><img :src="$store.state.tenantInfo.logo | fullPath" /><p style="font-size: 18px">商家端</p><input type="tel" placeholder="用户名" v-model="formData.username" /><input type="password" placeholder="密码" v-model="formData.password" /><div class="verification"><input type="text" placeholder="验证码" v-model="formData.verifyCode" /><div @click="reflashCode" id="imgcode"><img :src="imgUrl" /></div></div><input @click="login" type="button" value="登录" /></div><div v-else-if="type == 1" class="form"><img :src="$store.state.tenantInfo.logo | fullPath" /><h3>请点击下方按钮授权</h3><p>授权后,你将开通微信提现功能,提现时金额将到此微信</p><inputid="authorization"@click="updateOpenid"type="button"value="一键授权"/></div><div v-else-if="type == 2" class="form"><img :src="$store.state.tenantInfo.logo | fullPath" /><p>授权成功,你已开通微信提现功能</p></div><div v-else-if="type == 3" class="form"><img :src="$store.state.tenantInfo.logo | fullPath" /><p>分站已开通微信提现功能,授权的微信账号与当前账号不一致,是否重新授权</p><inputid="authorization"@click="updateOpenid"type="button"value="重新授权"/></div></div></template><script>import rsa from "@/utils/rsa";export default {name: "",data() {return {type: -1, //1未授权,2已授权(授权openid与当前openid一致),3已售罄(授权openid与当前openid不一致)formData: {username: "",password: "",verifyCode: "",verifySign: "",},imgUrl: "", //图片验证码路径code: "",openid: "", //当前openid};},created() {this.reflashCode();var openid = this.$route.query.openid;if (openid) {this.openid = openid;}var token = localStorage.getItem(`dftToken`);if (openid && token) {this.decideOpenid();}},mounted() {},watch: {},methods: {//-----------获取图片验证码------------reflashCode: function () {var _this = this;_this.$axios.get("/ReportApi/basic/system/getVerifyCode", { responseType: "blob" }).then(function (res) {_this.imgUrl = window.URL.createObjectURL(res.data);_this.formData.verifySign = res.headers.sign;}).catch(function (error) {//consloe.log(error);});},//-------登录--------login: function () {var _this = this;if (_this.formData.username == "") {_this.$message.warning("账户名不能为空");return;}if (_this.formData.password == "") {_this.$message.warning("密码不能为空");return;}if (_this.formData.verifyCode == "") {_this.$message.warning("验证码不能为空");return;}// let postData = _this.$qs.stringify(_this.formData);_this.$request.post({url: "bus/info/public/authority",params: {account: rsa.encryptByPublicKey(_this.formData.username),password: rsa.encryptByPublicKey(_this.formData.password),verifyCode: _this.formData.verifyCode,verifySign: _this.formData.verifySign,},success: (res) => {_this.type = 1;localStorage.setItem("dftToken", res.token);localStorage.setItem("phone", res.info.phone);this.$mit("getUserInfo", res);this.$mit("setIsLogin", true);this.toWechat();},});},updateOpenid() {this.$request.post({url: "bus/info/updateOpenid",params: {openid: this.openid,},success: (result) => {this.$message.success("授权成功");this.type = 2;},finally: () => {},});},decideOpenid() {this.$request.post({url: "bus/info/decideOpenid",params: {openid: this.openid,},success: (result) => {this.type = result;},finally: () => {},});},toWechat() {window.location =window.location.protocol +"//" +window.location.host +"/newApi/wechat/autho/public/send?type=2";},},};</script><style>html,body {width: 100%;height: 100%;min-width: auto;}input[type="button"] {-webkit-appearance: none;appearance: none;}</style><style scoped>#wxLogin {width: 100%;height: 100vh;background-color: #f5f5f5;}.form {padding-top: 100px;}.form > img {width: 40%;display: block;margin: 0 auto;margin-bottom: 20px;}.form > input[type="tel"],.form > input[type="password"] {width: 80%;height: 40px;display: block;margin: 0 auto;margin-top: 20px;border: none;border-radius: 50px;background-color: #ffffff;padding: 0 20px;box-sizing: border-box;font-size: 16px;}.form .verification {width: 80%;height: 40px;margin: 0 auto;margin-top: 20px;display: flex;justify-content: space-between;}.form .verification input {background: #ffffff;border: none;border-radius: 50px;width: 60%;height: 100%;padding: 0 20px;box-sizing: border-box;font-size: 16px;}.form .verification #imgcode {width: 38%;height: 100%;padding-right: 10px;cursor: pointer;margin-left: 10px;}.form .verification #imgcode img {width: 100%;height: 100%;display: block;border-radius: 5px;}.form input[type="button"] {width: 80%;height: 40px;display: block;margin: 0 auto;margin-top: 20px;background-color: #ff8800;border: none;border-radius: 50px;color: #ffffff;font-size: 18px;letter-spacing: 20px;padding-left: 20px;box-sizing: border-box;}.form input[type="button"]#authorization {letter-spacing: 2px;padding-left: 2px;box-sizing: border-box;width: 60%;}.form h3 {font-size: 16px;color: #4d4d4d;margin-top: 10px;text-align: center;}.form p {text-align: center;color: #808080;font-size: 14px;margin-top: 10px;margin-bottom: 10px;}</style>
export default {// 验证手机号isPhone(phone){var reg=/^1[3-9]\d{9}$/;if(reg.test(phone)){return true;}else{return false;}},/*** 验证手机号中国(严谨), 根据工信部最新公布的手机号段* @param { string } value*/isPhoneStrict(phone){var reg=/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-7|9])|(?:5[0-3|5-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1|8|9]))\d{8}$/g;if(reg.test(phone)){return true;}else{return false;}},// 数字和字母numandenglish(str){var reg=/^[A-Za-z0-9]+$/;if(reg.test(str)){return str;}else{return str.replace(/[^\w\.\/]/ig,'')}},// 验证邮箱isEmail(email){var reg=/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;if(reg.test(email)){return true;}else{return false;}},// 验证非中文noChinese(text){var reg=/^[\u4e00-\u9fa5]{0,}$/;if(reg.test(text)){return '';}else{return text;}},//验证正整数(不含零)isInteger(num){var reg=/^[1-9]\d*$/;if(reg.test(num)){return num;}else{return num.replace(/[^\d]/g,'');}},// 验证正数(不含零)// 可在keyup方法中使用// num 需要验证的数字// min 需要限制的最小值// max 需要限制的最大值isPositive(num,min,max){var reg=/^\d+(\.\d+)?$/;// 小数点出现次数var n = (num.split('.')).length-1;// 小数点不止出现一次if (n > 1) {return num.substr(0, num.length - 1);}// 判断最后一位为小数点if (num.indexOf(".") != -1 && num.indexOf(".") == (num.length - 1)) {if (num.length > 1) {return num;} else {return ''}} // 最后一位不是小数点,判断是否为数字,判断小数位数else if(reg.test(num)){if(min && Number(num)< min) return min;if(max && Number(num)> max) return max;// 小数点后位数大于2,返回两位小数if (num.indexOf(".") != -1 && num.split('.')[1].length > 2) {return Number(num).toFixed(2);}if(Number(num)>= 0) return num;return '';}// 不纯为数字,清空并返回空else{return num.replace(/[^\d(\.\d)?$]/g,'');}},// 验证链接isLink(link){if(link.indexOf('https://') == -1 && link.indexOf('http://') == -1){return false;}else{return true;}},/*** 验证银行卡号(10到30位, 覆盖对公/私账户, 参考微信支付)* @param { string } value*/isBanktNumber(value){if (/^[1-9]\d{9,29}$/g.test(value)) {return value;} else {return '';}},/*** 验证座机电话(国内),如: 0341-86091234* @param { string } value*/isLandlineTelephone(value){if (/\d{3}-\d{8}|\d{4}-\d{7}/g.test(value)) {return value;} else {return '';}},/*** 身份证号, 支持1/2代(15位/18位数字)* @param { string } value*/isIDCard(value){if (/(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/g.test(value)) {return value;} else {return '';}},/*** 验证护照(包含香港、澳门)* @param { string } value*/isPassport(value){if (/(^[EeKkGgDdSsPpHh]\d{8}$)|(^(([Ee][a-fA-F])|([DdSsPp][Ee])|([Kk][Jj])|([Mm][Aa])|(1[45]))\d{7}$)/g.test(value)) {return value;} else {return '';}},}
搭建效果如下
网站: http://124.223.62.76/#/index
礼品代发网
搭建起来的效果如上演示,因为篇幅有限源码会在其他教程体现,以上就是如何从0搭建一个礼品网的教程。